Search Unity

  1. Unity Asset Manager is now available in public beta. Try it out now and join the conversation here in the forums.
    Dismiss Notice

Unity Future .NET Development Status

Discussion in 'Experimental Scripting Previews' started by JoshPeterson, Apr 13, 2021.

  1. Thaina

    Thaina

    Joined:
    Jul 13, 2012
    Posts:
    1,166
    The problem you talk about is only just a problem of Task and ValueTask, not a problem of async in and of itself

    The root cause is really because wrong implementation and expectation of Task that was begin carelessly and now become legacy. async/await itself is just a way to flatten the callback hell and make it become clearly imperative. Not only C# but other high level language start adopting this pattern

    Rust is considerably low level by design so they avoid this pattern in general. But in reality async/await is just syntactic sugar and can be implemented or tweaked in anyway if it serve it purpose

    It's not just about IO. It about promise. Whenever you told something to do some task and you only care about the result when it finished you then want to continue the process, that's all there is for the abstraction. And so they decide this paradigm as it is, you wait for it and the function need to tell it was a function that might not return it result immediately, thus async

    It is very sensible and what actually missing is IAsyncEnumerable that was not just came out at the same time
     
    Trigve, MasonWheeler, Qbit86 and 2 others like this.
  2. Kamyker

    Kamyker

    Joined:
    May 14, 2013
    Posts:
    1,091
    And then there's me using jobs with async that require sync points. Complex logic looks simple:
    Code (CSharp):
    1. async UniTaskVoid RunJobs()
    2. {
    3.     try
    4.     {
    5.         while(true)
    6.         {
    7.             //start at the end of prev fame
    8.             await UniTask.Yield(PlayerLoopTiming.LastPostLateUpdate, t);
    9.             await initJob.Schedule()
    10.                 .CompleteOn(PlayerLoopTiming.LastEarlyUpdate, t);
    11.             //can now use player inputs as I process them in EarlyUdate
    12.             await jobWithPlayerInputs.Schedule()
    13.                 .CompleteOn(PlayerLoopTiming.LastUpdate, t);
    14.             //update done
    15.             await jobForRenders.Schedule()
    16.                 .CompleteOn(PlayerLoopTiming.PostLateUpdate, t);
    17.             //render
    18.         }
    19.     }
    20.     catch(OperationCanceledException e) { }
    21. }
    22. void OnDisable()
    23. {
    24.     tokenSource.Cancel();
    25. }
    26.  
    Without async this code was a complete mess taking x3 amount of code with prob worse performance when
    MB callbacks were used.
     
    Nad_B, oscarAbraham, Trigve and 6 others like this.
  3. OBiwer

    OBiwer

    Joined:
    Aug 2, 2022
    Posts:
    61
  4. JoshPeterson

    JoshPeterson

    Unity Technologies

    Joined:
    Jul 21, 2014
    Posts:
    6,936
    We're not planning to go to C# 10 before moving to use CoreCLR as a JIT runtime and the CoreCLR base class library. C# 10 has a number of features that are tied pretty closely to the runtime and and BCL, so we're putting our effort into updating everything rather than implementing features in Mono to support C# 10.
     
  5. OBiwer

    OBiwer

    Joined:
    Aug 2, 2022
    Posts:
    61
    Thanks for clarifying!
    On that note, Is there a reason why this thread is not covered at all in Engineering tools roadmap | Unity? The posts on the first page are updated last time 11 Months ago, it's also not super straight forward how the edits are made. This thread is 31 pages long.
     
    Nad_B, domportera and Eclextic like this.
  6. JoshPeterson

    JoshPeterson

    Unity Technologies

    Joined:
    Jul 21, 2014
    Posts:
    6,936
    I'm not sure why that has not been updated. I'll follow up internally though. Thanks!
     
  7. Igotlazy

    Igotlazy

    Joined:
    Jan 15, 2018
    Posts:
    65
    Speaking of updating, would it be possible to update the second post in this thread with current ongoings/progress? Been a bit since the last edit and ya there's a whole lotta pages in here.
     
  8. Eclextic

    Eclextic

    Joined:
    Sep 28, 2020
    Posts:
    142
    You mean "C# Serialized Data Migration"?
     
  9. CaseyHofland

    CaseyHofland

    Joined:
    Mar 18, 2016
    Posts:
    613
    That actually reminds me, @JoshPeterson did you guys consider this internally? In a modern Unity, it would be great if we could serialize everything. Things like Dictionary and Timespan really don't need an interpreter, but Unity insists on it because... I guess there's no Inspector implementation for them?
     
    Nad_B and Eclextic like this.
  10. Deleted User

    Deleted User

    Guest

    You guys should check out Unity's Serialization library and Unity's Properties library which is bundled into the editor as of version 2022.2.

    Based on its features and implementations, it looks like there may be an internal push to update unity's serializable properties. There are ways in the Properties library to register properties for types you do not have control over. I think you could add a property bag each for the Dictionary and Timespan types.

    EDIT: I wanted to add, the properties UI package, which is based on properties and will generate editor UI. This makes me think there's some kind of internal overhaul for this.
     
    CaseyHofland likes this.
  11. Thaina

    Thaina

    Joined:
    Jul 13, 2012
    Posts:
    1,166
  12. JoshPeterson

    JoshPeterson

    Unity Technologies

    Joined:
    Jul 21, 2014
    Posts:
    6,936
    There has been some movement within Unity about this (as others have posted about). My team is not directly involved though, so I don't have too many details. In general, there is a push to do more in C#, so I suspect this might be an option for the future.
     
  13. JoshPeterson

    JoshPeterson

    Unity Technologies

    Joined:
    Jul 21, 2014
    Posts:
    6,936
    Good point, that was pretty out of date. I've updated it and linked to our blog post about the future plans.
     
  14. Thaina

    Thaina

    Joined:
    Jul 13, 2012
    Posts:
    1,166
    Are there any possibility to add support just for only generic math feature from C# 11 ?

    Given that unity as game engine is relied on math more than anything else. Math relate feature like this should be prioritized
     
  15. JoshPeterson

    JoshPeterson

    Unity Technologies

    Joined:
    Jul 21, 2014
    Posts:
    6,936
    I don't expect we will do that before we have CoreCLR shipping as a JIT runtime. The current guidance is to use Unity.Mathematics + Burst for high performance math.

    I'm curious though if C#11 generic math offers some advantages of that. I'm not familiar enough yet with the generic math to know the answer.
     
    Nad_B, Eclextic, cxode and 2 others like this.
  16. Mindstyler

    Mindstyler

    Joined:
    Aug 29, 2017
    Posts:
    248
    'Generic math' is marketed as such by the .net team, but it's much more than that and not specific to math. Mainly it's support for abstract static methods and operators which is an incredibly powerful addition to a devs toolset.
    Burst could use this extensively to allow users to write easier / more directly mapping code because burst can only work with static methods anyway.
    That said, atm Unity specifically doesn't miss out on much.

    Furthermore, the 'abstract static methods' feature is *not* syntactic sugar but requires changes in the runtime, so adding it to Unity before the CoreCLR transition is not possible anyway.
     
    Qbit86, OBiwer, Thaina and 1 other person like this.
  17. Eclextic

    Eclextic

    Joined:
    Sep 28, 2020
    Posts:
    142
    It basically allows one to have a common way of adding, subtracting, multiplying and dividing while also having a way of declaring a value that represents "one" and "zero".

    This would be IMMENSELY useful in Vector classes and such!

    See for yourself!
    Static abstract methods in interfaces - C# 11.0 draft specifications | Microsoft Learn
    INumber<TSelf> Interface (System.Numerics) | Microsoft Learn
     
    Anthiese and Thaina like this.
  18. Thaina

    Thaina

    Joined:
    Jul 13, 2012
    Posts:
    1,166
    To be honest I have very high expectation about this feature so that we could write all utility math function we need to use as generic that can work with wide range of struct far more cleanly and efficiently

    It was not as crucial as to die for. But it is unfathomably make life easier

    Currently now we need to make multi function or class that do exactly same job for vector2/3/4 float2/3/4 and so on and so on. Many just give up and using System.Convert or reflection. And the one who don't have a very hard time maintaining all code that support each of struct. While it was actually always able to write with ultimately infinite level of abstraction with generic math

    As people above me have mention. It could be ultimately boost the convenient to use and make utility library or functions to work with DOTS and also every bits of unity in general
     
    Eclextic likes this.
  19. TieSKey

    TieSKey

    Joined:
    Apr 14, 2011
    Posts:
    225
    As a workaround for now, don't forget about code generators.
     
  20. Mindstyler

    Mindstyler

    Joined:
    Aug 29, 2017
    Posts:
    248
    Source generators are incredible, but they do NOT cover any use case that 'abstract static members' covers.
    Also people, while yes, this feature is great for math and all, do not dismiss the ultimately wider range of use cases outside of math-centric code.
     
    Anthiese and Eclextic like this.
  21. CaseyHofland

    CaseyHofland

    Joined:
    Mar 18, 2016
    Posts:
    613
    The thing is, generic math does not cover vectors in this way... perhaps one day, but for now it looks like this:

    Code (CSharp):
    1. static T DoStuff(T lhs, T rhs) where T : INumber // Valid Code
    2.  
    3. static T DoStuff(T lhs, T rhs) where T : like, a vector, but with an INumber and they all match up // Wait which API is that?
    C# does not have Vector interfaces like that, so while
    Vector3<T> where T : INumber
    is technically possible, this won't be understood by Unity's Vector3 or float3 until there is an interface (or abstract class but no thanks) that lets us specify that that's what we're doing.

    (As a side note, doing this for vectors would be more complex. You know, I know, but for the audience vectors have a lot of math that may be specific to their dimension, and not all operations are valid when working with whole numbers so we have to account for those cases. This means that you can't just let IVector3 inherit from IVector2 and so on. So there's likely gonna need to be more abstract interfaces, something like INormalizable, and think carefully about this whole implementation-business first.)
     
    Last edited: Feb 3, 2023
  22. Mindstyler

    Mindstyler

    Joined:
    Aug 29, 2017
    Posts:
    248
    Currently it doesn't really suit whole vectors and such specifically. If / when the .net team introduces something for the built in vectors, then maybe that would make sense, but right now, it's really not considerable.
     
  23. Eclextic

    Eclextic

    Joined:
    Sep 28, 2020
    Posts:
    142
    But this is why abstract statics were implemented for?
    Imagine this, and yes an interface needs to be made, you create a Vector Interface that holds an array.
    Then also define some static abstract methods like Normalize, etc. so that the inheritor/contract abider implements those, and there you have it.

    X, Y, and Z can just be properties of Vector3 that read from that interface array and the same goes for Vector2 and 4 with the according number of properties.

    This actually allows the API to be very flexible and allows many types to be used!
    So idk what you mean by that more abstract interfaces are a bad thing... that's what they're designed for...
     
  24. Mindstyler

    Mindstyler

    Joined:
    Aug 29, 2017
    Posts:
    248
    1 - no abstract static members were *not* implemented specifically for vectors, not even for just math
    2 - an 'array implementation' like this looks waaaaay too sketchy for my taste. there are better ways
     
  25. Thaina

    Thaina

    Joined:
    Jul 13, 2012
    Posts:
    1,166
    Generic math in core library is just a subset of the generic math as a feature

    I mean "generic math as a feature" is ability of C# that allow you to define your own interface similar to IVector and IMatrix to make your own generic math

    Surely unity itself must also add more code, especially number interface, to the existing class and struct oy make is useful. But that was expectable
     
  26. CaseyHofland

    CaseyHofland

    Joined:
    Mar 18, 2016
    Posts:
    613
    I never said it was a bad thing, they’re awesome! I just said there are no vector implementations yet.
     
    Eclextic likes this.
  27. koirat

    koirat

    Joined:
    Jul 7, 2012
    Posts:
    2,073
    Problem with interfaces and structs is that in many circumstances your struct will be boxed.
     
    MasonWheeler likes this.
  28. Thaina

    Thaina

    Joined:
    Jul 13, 2012
    Posts:
    1,166
    I kind of understand what you try to say but I think you misunderstand a little

    What in my mind when I say "support generic math" is not about current generic math interface. What I mean is the ability to define interface to include static member and operator to do math in and of itself. And this also recursively allow member of any struct or class to utilize the genericity

    For instance. We may have

    Code (CSharp):
    1.  
    2. public interface INumberContainer<T> where T : struct,INumber
    3. {
    4.    public static int Size { get; }
    5.    public ref T this[int index] { get; }
    6. }
    7.  
    8. public interface IVector3<V,T> : INumberContainer<T>, IIIIIII /** Almost all interface similar to INumber, but not the INumber itself **/
    9.                                                     where V : IVector3<V,T> where T : struct,INumber
    10. {
    11.    public static int Size => 3;
    12.    public ref T this[int index] { get; }
    13. }
    14.  
    15. //// use case
    16.  
    17. public class OneEuroFilter<T> where T : IAdditionOperators<T,T,T>,IAdditiveIdentity<T,T> /** Only operator need for the algorithm **/
    18. {
    19. }
    20.  
    I have been using OneEuroFilter code that was release for free and very frustrating for the confusion and boilerplate in the code just for making it work with multiple unity vector class. Same go for every similar utility class we try to make in unity

    Only generic math is the salvation
     
    Eclextic and koirat like this.
  29. Thaina

    Thaina

    Joined:
    Jul 13, 2012
    Posts:
    1,166
    If you utilize generic along with interface generic constraint carefully and thoroughly it will not get boxed, and that's the ultimate usefulness of static interface. You won't need to box and unbox anything if you define everything a struct can do with interface then constraint it on the generic class and function, you will able to pass by value or even by reference without boxing into object

    This is the true power of generic function that many people underestimate. And this leads to the problem that people misunderstand that C# cannot write high performance code cleanly. It actually because we don't make library that utilize generic and more struct (partly because we don't have static member define in interface like in C# 11)

    Seriously I could say people have been use interface in the wrong way for the whole 20 years life of C# language

    Code (CSharp):
    1.  
    2. public ISomeInterface {  }
    3.  
    4. public void DoSomething(ISomeInterface x)
    5. {
    6. }
    7.  
    This code above is totally wrong. It will get boxed when you pass struct

    But this code below

    Code (CSharp):
    1.  
    2. public ISomeInterface {  }
    3.  
    4. public void DoSomething<T>(T x) where T : ISomeInterface
    5. {
    6.  
    7. }
    8.  
    Will not get boxed

    Interface is always misused as direct type like this. It actually wrong design. Interface should not be allowed to used as variable or return type. It should only be used as generic constraint by default
     
    Last edited: Feb 4, 2023
  30. koirat

    koirat

    Joined:
    Jul 7, 2012
    Posts:
    2,073
    I get your point but you have exaggerated a little.
    I'm not sure you are referring to this specific case but generally Interface as return or variable type is perfectly fine.
     
    MasonWheeler likes this.
  31. Thaina

    Thaina

    Joined:
    Jul 13, 2012
    Posts:
    1,166
    It's only fine for reference type object and it was a design for language that only contain object like java or typescript. It break down whenever struct try to use it and be boxed, especially within language like C# that allow custom struct declaration

    And so it make every API that accept interface become plausible source of performance bottleneck by boxing

    Ideally if dotnet could be reworked it should design its system to avoid boxing. Whenever there is object or interface defined as variable or return type it should compiled to be IL that look like generic, ready to accept struct and do everything without boxing by default

    And I think I am not exaggerated because whenever there is a way to utilized more struct, people prone to mention struct is bad because boxing. Which tell me people always forgot that you can avoid boxing by making generic. So the actual problem is generic techniques is very neglected and people always use interface directly so it expectably inefficient from boxing. To be honest I am very frustrate that whenever a good thing will happen there would be someone came up and talk about boxing struct is bad blah blah blah and so we got distracted and it cause any feature relate to struct not progressing

    This is the one reason we have problem currently. Many problem since 10 years ago can be solved with this new feature in C# 11. But it came too late because people just stuck with old dogma that
    struct + interface = boxing
    without stop and wonder about generic. So people don't use struct as much as it should. And so any good feature relate to struct are being neglected. And it cause struct to be underrated, this is become vicious cycle that people not try to use struct and struct feature are not prioritized and so people cannot try to use struct and I am very disheartened

    Which leads me to conclude that, because people are like this and we can't rework dotnet easily, Using interface directly should be prohibited. And people should be forced to use more generic whenever possible
     
    Last edited: Feb 4, 2023
    Trigve, SealDev, ontrigger and 3 others like this.
  32. Mindstyler

    Mindstyler

    Joined:
    Aug 29, 2017
    Posts:
    248
    I have a question to the general Unity team:
    why are there so many identical api methods around that just pass certain defaults as arguments to their respective same-named method with more parameters instead of using the default-parameter feature of c#?

    example:

    Code (CSharp):
    1.         public static T FindObjectOfType<T>() where T : Object
    2.         {
    3.             return (T)FindObjectOfType(typeof(T), includeInactive: false);
    4.         }
    5.  
    6.         public static T FindObjectOfType<T>(bool includeInactive) where T : Object
    7.         {
    8.             return (T)FindObjectOfType(typeof(T), includeInactive);
    9.         }
    instead of (imho better):
    Code (CSharp):
    1.         public static T FindObjectOfType<T>(bool includeInactive = false) where T : Object
    2.         {
    3.             return (T)FindObjectOfType(typeof(T), includeInactive);
    4.         }
     
    Eclextic likes this.
  33. Thaina

    Thaina

    Joined:
    Jul 13, 2012
    Posts:
    1,166
    This feature was introduced in C# 4.0

    There was once upon a time unity still stuck with mono which mostly equivalence to C# 3.5. And even new version was introduced it still need to retain code pattern and the movement are as sluggish as using dotnet core directly in unity currently. No one want to break pattern or go fix every API all at once
     
    Last edited: Feb 4, 2023
  34. Eclextic

    Eclextic

    Joined:
    Sep 28, 2020
    Posts:
    142
    First of all an engine SHOULD change in its lifetime,
    and second of all there is no need to fix them all at once.

    They can just update their internal code slowly but surely, but even that isn't happening... :(
     
  35. Mindstyler

    Mindstyler

    Joined:
    Aug 29, 2017
    Posts:
    248
    Also changing these method signatures over doesn't even break existing code.

    And i'm still a firm believer in a 3 year support/deprecation cycle anyway. Allows for good improvement / innovation on legacy code and isn't burdening projects all too much.
     
    Ghat-Smith and Eclextic like this.
  36. Thaina

    Thaina

    Joined:
    Jul 13, 2012
    Posts:
    1,166
    Another reason, I don't know if it really a case, is delegate signature

    We can make
    Action a = FindObjectByType;
    for that it have explicit overload operator

    Might have similar related reason about function signature if it utilized somewhere in unity
     
    Anthiese likes this.
  37. tannergooding

    tannergooding

    Joined:
    Jun 29, 2021
    Posts:
    29
    Happy to partner with the Unity team on any generic math or vector/simd related work ;)

    Let me cover a few points:

    1. Static Virtuals in Interfaces is a general purpose feature on which Generic Math is built. It allows you to define new and more useful kinds of abstractions particularly such that you can reduce code duplication and even avoid reflection. One useful scenario is "reflection free dependency injection" by virtue of being able to declare something such as:
    ```
    public interface ICreatable<T>
    {
    static abstract T Create();
    }
    ```

    You can of course make this take in any number of args (including generic args), can constrain it such that `where T : ICreatable<T>`, etc.

    2. The exact handling of generics is runtime dependent and what RyuJIT does is not necessarily what Mono does and it may not be what Burst does/etc.

    In the case of Mono they have "USG" (universal shared generics). What this means is that `Method<int>` and `Method<long>` and `Method<string>` all get the same shared method to handle their logic. There is no "specialization" and so the method body effectively pessimizes value types due to needing to handle any potential value type it can encounter and the fact such things are passed around by value, cannot trivially do virtualized dispatch, etc -- I do not have the context on whether or not Unity, which uses Mono, or Burst does anything different here.

    In the case of RyuJIT, we have code sharing for reference types via a type called `System.__Canon` and always specialize value types. For reference types this is generally acceptable because everything is a pointer and you won't be directly accessing fields or anything, only method calls so its at worst a virtual lookup. For value types, this means that `Method<int>` and `Method<long>` each get their own separate method body which helps ensure things stay nice and efficient. It also allows devirtualization (all value types are sealed), avoiding boxing, and large amounts of dead code elimination. The downside is that this also means that `Method<int>` and `Method<uint>` get different method bodies. This can lead to unnecessary code size increase when the bodies end up compiling to the same thing (which doesn't always happen, but can).

    For RyuJIT, this specialization means that the following pattern is not nearly as expensive as it looks:
    ```
    public static T Add(T left, T right)
    {
    if (typeof(T) == typeof(int))
    {
    int lhs = (int)(object)(left);
    int rhs = (int)(object)(right);

    int res = lhs + rhs;
    return (T)(object)(res);
    }
    else if (...)
    {
    // ...
    }

    // ...
    }
    ```

    Because of the specialization the `typeof(T) == typeof(int)` check becomes constant `true` for `int` and `false` otherwise. This allows only the relevant handling to be kept and any other paths to be dropped as `dead code`.

    Likewise, RyuJIT then specializes `(int)(object)(left)` because it statically knows `left` can only be `int` here, so it elides the box/unbox and just copies the value directly. The same is true for the `(T)(object)(res)`, so the ultimate codegen of this is `mov eax, ecx` followed by `add eax, edx` (well actually `lea eax, [ecx+edx]`, but not everyone might understand that code).

    3. Generic math allows such code to be simplified further as we can now write a method such as:
    ```
    public static T Add<T>(T left, T right)
    where T : INumber<T>
    {
    return left + right;
    }
    ```

    Most methods will of course be a bit more complex than this, but that's what it allows and it still generates "optimal code". Likewise we still get specialization for value types in RyuJIT and so we can still do inlining, constant folding, and more.

    You can see a slightly more complex example in my blog post (https://devblogs.microsoft.com/dotnet/dotnet-7-generic-math/) or read more in the official docs: https://learn.microsoft.com/en-us/dotnet/standard/generics/math

    4. Generic math will indeed allow something like an `IVector` interface in the future. It is something I am very much interested in adding and it was included in the original design considerations for the feature.

    In particular the "ideal" interface is something like
    ```
    public interface IVector<T, TScalar>
    where T : IVector<T, TScalar>
    where TScalar : INumber<T>
    // inherit relevant generic math interfaces here
    {
    // define relevant APIs here
    }
    ```

    However, there are some considerations around "arbitrarily length vectors" vs "fixed length vectors". There is also the consideration that an existing generic type such as `Vector128<T>` or `Vector<T>` could not be updated to implement the interface because it would require constraining its `T` to be `where T : INumber<T>`. Since this is changing from a less restrictive constraint to a more restrictive constraint, it is considered a binary breaking change.

    I have an open proposal against the language to provide some means of bridging this gap: https://github.com/dotnet/csharplang/discussions/6308

    5. There is also the consideration that the API surface exposed by Unity for its vector types differs from what we exposed in the BCL. Unity in general has different naming conventions/guidelines that drastically differ from the official guidelines. This is not necessarily a bad thing and makes sense given its historical roots, usage in scripting, etc. It just means that if we defined such an interface, Unity may not necessarily be able to "trivially" pick it up.

    I would personally like to see better interoperability between the Unity Vector types and the .NET Vector types as well as general SIMD acceleration on the unity side. The rewrite I did of Matrix4x4 to enable SIMD acceleration provided up to 48x perf improvements to key methods, and there is still a lot more that could be done ;)

    The same ultimately goes for Unity/Burst getting support for the Hardware Intrinsics once they're able to use the latest versions of .NET.
     
    Nad_B, oscarAbraham, Fijit and 12 others like this.
  38. PetrisPeper

    PetrisPeper

    Joined:
    Nov 10, 2017
    Posts:
    66
    Changing a method signature can be a source breaking or a binary breaking change (or both). Changing to default parameter values would be a binary breaking one and would break all binaries compiled against old code.
     
    Trigve, Eclextic and Thaina like this.
  39. Thaina

    Thaina

    Joined:
    Jul 13, 2012
    Posts:
    1,166
    Salute to @tannergooding the great for elegant elaboration

    I too wish that in the future unity would utilized more System.Numerics in their system. Or at least allowed it in general functions
     
  40. Mindstyler

    Mindstyler

    Joined:
    Aug 29, 2017
    Posts:
    248
    which wouldn't matter at all since all unity project code needs to be recompiled anyway when upgrading unity and a unity-runtime substitution is not possible for a built project.
    this is a non-argument
     
    goncalo-vasconcelos and Eclextic like this.
  41. CaseyHofland

    CaseyHofland

    Joined:
    Mar 18, 2016
    Posts:
    613
    It’s actually part of the C# framework guidelines (see the last point).

    https://learn.microsoft.com/en-us/dotnet/standard/design-guidelines/member-overloading

    65BE2ECD-16BF-4E6A-B7B8-74527E5B8776.jpeg
     
    Last edited: Feb 4, 2023
  42. Eclextic

    Eclextic

    Joined:
    Sep 28, 2020
    Posts:
    142
    But what are default parameters for then?
    I find this argument silly as that is practically syntactic sugar built for this use case that allows easy modification to a method without changing 20 others...
     
    ontrigger, Spy-Master and Mindstyler like this.
  43. Mindstyler

    Mindstyler

    Joined:
    Aug 29, 2017
    Posts:
    248
    You are confused. If the parameters differ from fuction to function you should not chain them like

    Code (CSharp):
    1. private void Example(int myInt = 0; string myString = null)
    2. {
    3. }
    and only provide the arguments you want, but that has nothing to do with calling another function with an in-method created default value and exchanging that to use a default parameter.
     
  44. CaseyHofland

    CaseyHofland

    Joined:
    Mar 18, 2016
    Posts:
    613
    I’ve updated my answer to stick to facts.
     
  45. Mindstyler

    Mindstyler

    Joined:
    Aug 29, 2017
    Posts:
    248
    Unity code does not need to be CLS compliant
     
  46. Spy-Master

    Spy-Master

    Joined:
    Aug 4, 2022
    Posts:
    628
    Yeah.
    Guidelines for general .NET purposes where multiple languages are pulled in (possibly with their own class libraries like for F#) don’t hold up in Unity-land where virtually all code is C#, and such limitations get in the way of productivity in Unity. Pointers (not IntPtr) are also non compliant, but you do see that occasionally.
     
    Eclextic likes this.
  47. tannergooding

    tannergooding

    Joined:
    Jun 29, 2021
    Posts:
    29
    It's worth noting that some of the Framework Design Guidelines (FDG) have been relaxed in more recent years and you can see some of this in new APIs we expose in the BCL and in how we discuss certain topics in API review (which is live streamed every Tuesday morning, Seattle time).

    CLSCompliance used to "matter", but its not as important today and doesn't even get properly tracked by most compilers anymore. The basics are still covered of course, but many new runtime/language features haven't undergone and consideration as to "compliance" or not and don't impact whether or not the compiler warns, even where the IL representation of some of these features involves a modreq and therefore is by definition "non-compliant".

    Beyond that, there is the consideration around how it impacts overload resolution, that simply tacking on a new parameter to an existing method is a binary breaking change for an already shipped library, and that we've done numerous user studies that have shown a majority of developers don't actually understand default parameters and still end up specifying all parameters anyways.

    Whether these factors are important or not to you is going to be on a scenario by scenario basis and the breaking change factor is certainly less of an issue for applications (which often don't have downstream consumers) than it is for libraries.

    ----

    Want to touch also cover that most of the FDG do not get in the way of productivity in the slightest and that includes default parameters.

    In the typical scenario you're talking about considerations around consistency and versioning.

    For consistency it's about ensuring that developers can approach any part of your code base and have some intuitive understanding of how it works/functions and the behaviors that may come with an API. A library that intentionally deviates from the norms isn't necessarily bad, but it often does have a slightly higher learning curve.

    For versioning following the guidelines generally involves what is effectively a 1-2 line difference from what you'd otherwise do. In the case of default parameters it's a copy/paste of the signature and calling the new overload with more parameters. This has numerous benefits including both short term and long term in how your code is structured, able to be refactored or maintained, etc. It also benefits in terms of performance, the ease at which optimizations can be applied by a JIT/AOT, and it can even give meaningful insight into how your APIs are being used by clearly separating out callers which are utilizing customization vs those which are not.

    Being able to rapidly prototype is important, but it's also not necessarily the most important thing. There is a balance and everyone has their own preference on the "right" way. Some people find the tiniest rules to get in their way and others find not having structure to do the same. And regardless of which a given person does, nearly everyone has had a time where they've gone to refactor something, add a new feature, or even root cause a bug and found themselves complaining about their past self doing something that was ultimately inconvenient.
     
    oscarAbraham, Qbit86, apkdev and 6 others like this.
  48. CaseyHofland

    CaseyHofland

    Joined:
    Mar 18, 2016
    Posts:
    613
    Source

    Source

    Source

    I clearly underestimated how sensitive a topic this was but that’s exactly why sources are important, because it’s getting hard to separate fact from sentiment.

    While I was looking to make a stronger case for myself, I was quite surprised to learn that C++ actually endorses default arguments:
    https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rf-default-args

    It was the first thing to turn my head, in spite of the fact that I really enjoy the method overloading that’s common in C#, Unity as well as standard libraries.

    opinions matter of course, but at the very least a good source shows it’s a shared opinion
     
  49. Thaina

    Thaina

    Joined:
    Jul 13, 2012
    Posts:
    1,166
    @CaseyHofland tannergooding himself is the official member of dotnet team
     
  50. Spy-Master

    Spy-Master

    Joined:
    Aug 4, 2022
    Posts:
    628
    Virtually all code being C#: Only shipping with csc, with vague suggestion that other languages may work. Aye, not really evidence. Evidence to the contrary is welcome.
    Losing productivity: you get a net loss in productivity with any set of rules that don't need to be there. An argument can of course be made that things will get more confusing for a consumer with poor use of such freedom in the API. At that point, it's the responsibility of the architect to make use of tools available to them or not. As a concrete example, taking "Default arguments are not CLS compliant." at face value, that in particular is something that absolutely tanks productivity if you just need to replace a few defaults (linked example below).

    Cases of CLS compliance not mattering to Unity, indicating "Unity code does not need to be CLS compliant":
    Methods using unsigned types besides byte (non-compliant) in place of signed types.
    API exposing pointers (non-compliant) over IntPtr.
    Default arguments are used in place of however many overloads that would be, which makes sense and is reasonable to do given the scenario.

    Technically, that doesn't change anything of what the boy is asking for (at least from my point of view, often in the camp of "credentials don't imply validity"), but that's good to know and cool.
     
    Eclextic and CaseyHofland like this.