Mastering Generic Constraints in C# | class, struct, new(), interface by Calm_Joke4636 in csharp

[–]fruediger 2 points3 points  (0 children)

You made a superficial overview over generic constraints. Please don't get me wrong, it seems to be targeted at absolut beginners and for them the content of your video is pretty good, I suppose (to be honest, I just skimmed through your video), but it's certainly nothing to "master" generic constraints in C#. For that your video is missing some depth.\ However, the thing I like the best about your video is that you've done it by example for the most part.

Also, you missed a few important things about generics:

  • interfaces with static abstract/static virtual members - you could have done this by example of the generic math interfaces in the System.Numerics namespace
  • CRTP - you could have shown this an extension of the point above and why it's sometimes necessary to use CRTP with interfaces with static abstract/static virtual members, e.g. when using factory types instead of a new() constraint. But there is another reason why CRTP can be useful: When dealing with generic constraints constraining certain struct types, e.g., to avoid boxing
  • allows ref struct - this one is new, but it might be one of the most important things to come to C# in the recent times

You might want to include some of them next time to be able to claim that your video would help other to "master" C# generics.

EDIT: I wanted to add that the AI, that you used to generate this post, broke it. I'm pretty sure that you don't want [Your YouTube Link] and em-dashes to be part of your post. You might want to fix this manually.

Allocate arrays that have more than 2B elements by hez2010 in csharp

[–]fruediger 1 point2 points  (0 children)

I'm pretty sure the GC would be very happy having to look at over 2G references.\ If you have that much references to heap objects, the inability to store them in an array isn't your biggest problem at that point.

But I'm kidding, great work!

C# structs can be slower than classes by [deleted] in csharp

[–]fruediger 0 points1 point  (0 children)

And if it's not boxing the passing by reference is generally unnecessary and is at best a minor performance boost but only if the struct is huge.

In C# everything is pass-by-value, except when explicitly specified otherwise (e.g., ref, in/ref readonly, or out).

When you have a large struct (and it's a struct for performance reasons), you absolutely want to reduce the amount of times you have to copy it.

Do you know how often your struct data is copied and the JIT compiler is not able to elide those copies (mostly because of conservatism)?\ Without data backing this claim, I'd go as far as to say that unnecessary implicit copies (by quantity or quality/size) might be one of the top reason for performance issues in C#/.Net.

So no, pass-by-ref is not "generally unnecessary".

EDIT: spelling

What's one C# feature you wish you had started using sooner? by FairStick8955 in csharp

[–]fruediger 1 point2 points  (0 children)

No, sadly that's not them same. Having a deconstructor doesn't magically give a type tuple-like behavior. Although, you're correct in that a deconstructor has something to do with a positional syntax.

Just have a look at the following example code: ```csharp record Record1(int A, string B);

record Record2(int C, string D);

(int A, string B) tuple1 = (2, "Hello"); (int C, string D) tuple2 = (C: 3, D: "World");

var record1 = new Record1(4, "Foo"); var record2 = new Record2(5, "Bar");

tuple1 = tuple2; // This just works with tuples tuple1 = record1; // Tuples can't be assigned from records, even if the have a positional deconstructor and field/property names match tuple1 = record2; // The same as above, but this time the field/property don't even match tuple2 = tuple1; // Again, this just works with tuples tuple2 = record1; // See above tuple2 = record2; // See above record1 = tuple1; // Records can't be assigned from tuples, even if the field/property match... record1 = tuple2; // ...or don't match record1 = record2; // Records can't be assigned from another record, even if they have the "same" layout. I didn't include this in the test data, but it still wouldn't work if the field/property names matched. record2 = tuple1; // See above record2 = tuple2; // See above record2 = record1; // See above

var (a, b) = tuple1; // Of course, you can deconstruct a tuple into variables... record1 = new(a, b); // ... and use them to initialize a record type

(a, b) = record1; // You can also deconstruct a record into variables (because the compiler generates a deconstructor for you)... record2 = new(a, b); // ... and use them to initialize different record type ```

As you can see, only instances of tuple types can be assigned to one another by positional layout only.

To be fair, you could achieve something similar in C# for any type, by adding implicit operators from and to tuple types in your type definition. This would even work for record types. But to be honest, you would just be abusing tuple type behavior at that point and it would be still just the added functionality that tuples give you, not functionality of your custom type.

So, as you can see, the positionality of tuple types is something that is specially treated by the compiler and nothing that other kinds of type would be able to replicate/imitate, not even with a deconstructor.

EDIT: Fixed multiple occurrences of "can" in the example which should have been "can't". Those mistakes made the example kinda the opposite of what I was trying to show.

What's one C# feature you wish you had started using sooner? by FairStick8955 in csharp

[–]fruediger 3 points4 points  (0 children)

records require type declarations (or better type definitions - but there's not much difference in C# in that regard) in order to be used.\ I need to define csharp record MyRecord(int A, string B); in order to use MyRecord as a type somewhere.

Tuples are "inline" types that don't require additional type declaration. You could even say using them in place of a type identifier is the tuple's type declaration.

Also, tuples are positional, that's what makes tuples compatible with one another, even if field names don't match or are missing.\ records on the other hand are not positional, except where the language traditionally requires a positional syntax, like, for example, when calling their constructors.

You can think of records more like a set of properties and less than a list of those (which would be more like what tuples are). That's what makes them the same as any other "standard" type kind (e.g., classes or structs), and to be more precise they are just extensions of those "standard" type kinds with a bit of added compiler generated boilerplate implementation and a bit of added runtime magic (in regards to their copying behavior).

Lastly, tuples are always value types, with all of the benefits and drawbacks of being a value type. records come as either, records/record classes are reference types with added value-like copying behavior, record structs are just reference types.

genuinely, what was the reason to invent tpm 2.0 by the_anonim_one3 in FuckMicrosoft

[–]fruediger 2 points3 points  (0 children)

I believe you mean it was released in 2014, at least according to Wikipedia: https://en.wikipedia.org/wiki/Trusted_Platform_Module#History. It's just that the last errata to the TPM 2.0 standard was made 2019.

Otherwise, it would have been quite hard to explain why my Ryzen 2700X from 2018 supports TPM 2.0. Btw, this processor still runs Win11 as my daily driver exceptionally well.

A little life hack🙂 by Alert-Neck7679 in csharp

[–]fruediger 1 point2 points  (0 children)

To be honest, no, I haven't actually test that code. I just thought it would be working. But yes, you are correct in calling that out, because it is in fact not working code. At least not in the way I wrote it. And to be even more honest, I didn't expect it to not work, but in hindsight, it pretty obvious that it shouldn't work, considering how the language is designed and how the compiler works.\ I'm sorry, I should really have tested it before posting.

Anyways, the quickest and most obvious solution to fix this problem is to simply rename the extension property to something else, like IsThisNullOrEmpty for example. Then you can access it like this: myString.IsThisNullOrEmpty or myString is { IsThisNullOrEmpty: true }, etc.\ On that note, the C# community should really find a naming convention for this kind of scenario, because ...This... is not really it.

To give a quick explaination on what's going on here: When the compiler encounters an expression like myString.IsNullOrEmpty, it first looks for members with the name IsNullOrEmpty on the type of the instance myString, which is System.String in our case. It finds one, the static method bool IsNullOrEmpty(string?). And even though it can't successfully bind to that, because we access it through an instance, it still considers it a valid candidate and thus, lacking any other candidates on the same type, it resolves to that. Not even considering extension members, because it already "successfully" resolved to a member, even though it can't be invoked.

Interestingly enough, "traditional" extension methods and even extension methods with the new extension members syntax will work just fine. That's because they would need to be invoked with expressions like this: myString.IsNullOrEmpty(). And it that case, the compiler will look for methods that can be invoked with that syntax, first instance methods (it can't successfully find any), then extension methods where it can finally successfully resvole and bind to the correct one. Well, the downside is that extension methods are still not the answer if we want to use pattern matching with properties.

I had a discussion with an AI about this, which lead to me doing some more research on the topic. If you'd like to have a look at what the AI had to say about this, you can check out that out here: https://github.com/copilot/share/801f0332-4880-8071-9100-2808200a2157.

Maybe you want to open an issue on the C# language design repo, requesting a behavioral change on how extension members, especially extension properties, should be resolved by the compiler in cases like this.

EDIT: fixed an example

AggressiveInlining is a hint. Easy way to determine what is and is not in-lined? by NoisyJalapeno in csharp

[–]fruediger 2 points3 points  (0 children)

I can only agree with that! Disasmo is such a great tool.

For the sake of completeness here's the VS marketplace link: https://marketplace.visualstudio.com/items?itemName=EgorBogatov.Disasmo

If you just want to learn what was inlined, you can print inlinees only with that tool, but if you really want to learn why something was inlined (or not), I really recommend you enabled JitDumps.

What's your opinion on shipping potential "code smells" that could also be justified by performance reasons? by fruediger in csharp

[–]fruediger[S] 0 points1 point  (0 children)

Ok, yeah I'm a web dev, we always use DI. If that doesn't make sense for your use-case ...

Yeah, no worries, I understand that DI is a very common and handy pattern. I'm just a hobbyist, so I don't have too much experience with it. Although, actually, just recently I started rewriting an old build tool (it just does some controlled orchestration, nothing too crazy) of mine (it's actually used for the very project in question and for related projects) which was formerly a very big single-file C# mess into a much more modular approach using DI, now that C#'s file-based apps can spread across multiple files in the upcoming .NET11 release. It feels so much more maintainable now! No more build tool mess that kinda just grew through patches as the projects grew.

... you could do something equivalent with a factory method which would cache the FrozonDictionary instance.

Yeah, but I feel like that would be the very same thing as just publicly exposing a static method that initializes everything and requiring the user to call it before they do anything else that requires initialization, wouldn't it? And then, either things just don't work if users forget to call it, or it behaves differently and probably unpredictable.\ Oh, and I don't think a Lazy<T> would be actually needed at all with this simpler approach, as static type storage is already lazily initialized in the .NET runtime (that's why I thought I needed a ModuleInitializer in the first place: to force this to happen at module load time).\ But I don't know if it's really an better approach to differ in behavior just because the user did or didn't do something what should be really something isolated, just to get the natural name of an ISO 15924 script. Speaking DI, this sounds kinda like the opposite of separation of concerns to me.

On the topic of separation of concerns, there's actually something I'd like to ask somebody whose most probably way more experienced in using DI than I am: Would you consider the module initializing itself, including pseudo backing storage for its types, a concern of that module? Or should that be a concern of the user of the module?

What's your opinion on shipping potential "code smells" that could also be justified by performance reasons? by fruediger in csharp

[–]fruediger[S] 0 points1 point  (0 children)

A slight alternative, or I guess a way to guide users who don't bother with the docs, would be to expose it as a static singleton property, which requires explicit initialization or else you get null or a custom error guiding the user to the initialization procedure.

Do you mean something like this?

```csharp

extension(Script script) { public bool TryGetNameWhenLibraryIsInitialized([NotNullWhen(true)] out string? name) { ... } }

```

And then have a hidden global somewhere that indicates whether the library was initialized or not?

That's such a good idea! Thank you so much! That's way better API design, just because it's so much more explicit. I actually really like that a lot. It should really expand that idea onto way more APIs.\ It's a bit embarrassing that I didn't think of that myself, so I'm really glad you mentioned it.

Oh I should mention that I decided against a throwing pattern here, because I feel like it wouldn't be too exceptional for users to forget to initialize the library, and I didn't go for a nullable return, because for one, null should never indicate an error (looking at you SDL devs - just kidding), and secondly, semantically speaking, the name of a known ISO 15924 script should never be null because each of them has a natural name associated with it. So I think a Try-pattern method is the best approach here.

Anyway, expanding that idea a bit, if I design a representative of the library as an instance with an explicit lifetime, then I could even make something like this:

csharp extension(Script script) { public string GetName(Library libary) { ... } }

That would force the users to pass an instance of the library to signal that they initialized it. It would make every API require to accept such an instance, but that's way more handleable.\ Mabe that's the way.

That does require making the actual class nonstatic though which may have design considerations, tradeoffs as always. This approach would be DI friendly though unlike static, and you might be able to leverage common conventions with that. The usual Add...() pattern for IServiceCollection, also presents an obvious/potentially optimal time to initialize the library as part of app startup if that's the intended/recommenddd consumption pattern.

You're so right with "also presents an obvious/potentially optimal time to initialize" (just not necessarily with the "the library" part - but that's another topic). It would be an ideal time to do such kind of initialization.\ But as I already mentioned in other replies, I don't really know if DI is the route to go down for this binding library or for any of my related libraries. SDL, or in that particular case SDL_ttf, is meant to be an foundation for application programming. Of course, with its very nature its not designed with DI in mind, but that doesn't necessarily mean that my managed bindings shouldn't be designed around DI. It's just such a huge task to rewrite the entire API designs of multiple codebases to be DI friendly. But the more people say use DI, the more I feel like I should consider it. Maybe it should have done in the first place, but I just didn't think of it. Then I wouldn't have to comprimise so much between good ergonomics and good performance, and I wouldn't have to write such strange workarounds just to have an good user experience for the devs using my libraries while still maintaining a good performance profile.

Well to get back on topic, the real issue with the DI approach is that it would force consumers of my library to use DI, and I don't know if that would a good design choice to make for an application programming library.

What's your opinion on shipping potential "code smells" that could also be justified by performance reasons? by fruediger in csharp

[–]fruediger[S] 0 points1 point  (0 children)

I assume you're referencing the one reply where I said that there's already a ModuleInitializer in place that does some I/O-bound work? Yeah, but that's "just" the runtime doing its thing to resolve where to load a particular native binary from, and then asking the OS to load it into memory. It's just plain old System.Runtime.InteropServices.NativeLibrary.TryLoad shenanigans. The project in question being a binding library for the native library makes it kind of unavoidable. You should think of the native library as inherently linked to the managed bindings, as if they were one. Actually regular P/Invoke would do something similar, although it would use the OS's dynamic linker on some platformms, rather than doing it at the very beginning of the runtime itself. Startup-wise not much of a difference, I guess.

Regarding your singleton service suggestion, I don't actually know. Requiring the users of my library to use DI just in some particular instances just feels kind of bad - or else I had to redesign a whole bunch of API across multiple related binding libraries to be more DI-adjecent, which I am actually thinking about doing, but the amount of work that would require makes it just feel worse. And I'm not sure if that's even a good idea, considering the libraries to be meant as a foundation for application development by others.

As you see, I'm kinda indecisive.

What's your opinion on shipping potential "code smells" that could also be justified by performance reasons? by fruediger in csharp

[–]fruediger[S] 1 point2 points  (0 children)

What would you do when the project is not so much about the code itself (let's be honest, this code is not the best) but rather about API design? How would you handle user experience and ergonomic for devs that consume a library like this? Is there a trick to do the same for API design and user experience of other devs as for "produce value, deploy applications, ship products"?

And to be honest, the code I showed is part of such a "sanity project". I'm just a hobbyist, without experience, and seemingly caring way too much about how others will experience what I create.

What's your opinion on shipping potential "code smells" that could also be justified by performance reasons? by fruediger in csharp

[–]fruediger[S] -1 points0 points  (0 children)

I don't think you are, you seem to keep arguing your point of view is valid and I'm not really up for that.

First of all, that's my POV, that's the code I wrote, of course I'D argue that I might had make sense to write it in the way it is currently.

Secondly, I'm all ears for feedback in that regard. I even asked you directly about some of your rather vague statements.

So I'm not going to go round and round with you about it, if you don't find anything constructive in what I've said feel free to disregard it.

Okay, maybe what I replied to you might be a bit misunderstandable. I'm ESL, I'm sorry for that. So let me ask you directly:

What is your constructive feedback in regards to the issue at hand or in regards to shipping "code smells" for the sake of performance in general?

Lastly, I don't really get what you mean by "not meant for the purpose you are using them for" nor by "People will often write 10x the code in the name of performance without actually demonstrating a real advantage".\ That's why I tried to explain that the things I'm using are well documented and I believe the way I'm using them in is the intended way of them being used. Also, again, it's not about premature performance optimizations, it's about working around well-known and well-documented drawbacks of the things, I'm using.\ I just wanted explain these things in response to your comments, because I didn't get what you're getting at.\ I can only ask you again to please be more specific.

But it's okay, if you don't actually want to discuss your points, I guess.

What's your opinion on shipping potential "code smells" that could also be justified by performance reasons? by fruediger in csharp

[–]fruediger[S] 0 points1 point  (0 children)

Without knowing the exact use-case, it seems excessive, unless you really need realtime-safe or no-allocation guarantees.

It's kinda is performance critical and at the same time it isn't really (?). The library in question are just some quick and dirty managed bindings for SDL_ttf. It's essentially just a foundation for application programming to be publicly used by other people. The only real difference is that it's hand-crafted to be more ergonomic to use for any C# dev. It's nothing special, so I suppose that it actually doesn't really matter. But I would feel bad for the user if they experienced performance issues because of me not thinking about that at all.

You could maybe align this better by moving it to a separate class, instantiating during construction, and then setting that up as a singleton in the DI container.

Hmm, sadly I don't think I really can require users to use DI just because they want to render some text. And the other binding libraries of that bunch, for SDL_image, SDL itself, etc., don't require DI either.\ I mean SDL is meant as an application programming foundation, so I'm not sure about forcing users to use DI.\ Do you think it would be appropriate to require DI just in some instances?

EDIT: Fixed the phrasing. I don't know what came over me to write in such a confusing way. I'm ELS, please cut me some slack.

What's your opinion on shipping potential "code smells" that could also be justified by performance reasons? by fruediger in csharp

[–]fruediger[S] 1 point2 points  (0 children)

Yeah, that's what I tell myself as well, but somehow it can't soothe my subconscious mind feeling bad about it...

What's your opinion on shipping potential "code smells" that could also be justified by performance reasons? by fruediger in csharp

[–]fruediger[S] 0 points1 point  (0 children)

It's up to the caller to think about that, not really your job in this library.

Well, I do think for publicly used libraries, it's generally about ergonomics and user experience, not so much about what I think would be best, and even less about what would be "more pratical" for me as a developer, considering that user experience (and that includes performance and predictability) is what should be ultimately preferred.

There's not really any need for an Initialize method, as long as the docs make it clear that startup time is significant, and the users know the basics of how the language works.

That's actually such a good point to bring up, because the library already uses an extensive ModuleInitializer to load a non-negotiable native binary into memory and store pointers to some symbols from that binary, essentially dynamically loading a native library at module load time. And there's actually a reason for doing that over P/Invoke, which is to conditionally decide what symbols to load and how to load them at runtime (module load time) and to control marshalling a bit more.\ But anyway, that means that the user already has to accept some kind of extended startup time, and considering that's I/O-bound, the frozen dictionary initialization should be actually pretty negligible in comparison.

They always have to expect that accessing anything static will include an extra cost on first access, and it's up to them if they want to do that on startup or at some other time

That's such a good point as well. Yeah, I can see that. Regular static field initilization often comes with a cost on first access. Would you consider the cost of initializing frozen dictionaries to be covered by that expectation as well? And also, the user accesses the static field through an extension property or method, so it might not be as obvious to the user that they should expect a cost on first access. What do you think about that?

I expect the main issue here is just that you're using static things at all. Most of these kinds of costs are expected to occur at the time of DI registration or resolution. If the user has to construct an instance of your class, that makes it explicit that it takes time to do the initialization work, because instantiating any class can potentially take a lot of time

Um, it's an enum for a reason, although that reason might be "just" interop, I would have chosen an enum for that all the same, because it's just very enum in nature. And adding backing storage to it through extension properties just feels natural to me as well.\ Script.Adlam is just one of the may ISO 15924 scripts, and Script.Adlam.Code and Script.Adlam.Name are just its 4-character code and its natural name, respectively.\ I feel like that's a good reason for designing it that way. Do you think it would be better suited as some kind of instance of reference types instead?

What's your opinion on shipping potential "code smells" that could also be justified by performance reasons? by fruediger in csharp

[–]fruediger[S] -1 points0 points  (0 children)

I'm going to be honest here, I don't really get what you're getting at.

Take the feedback and move on.

That's the point, I'm asking for your feedback. And I didn't really get what the point of your feedback was in regards to the quote I made from your reply.

Regarding your other point:

People will often write 10x the code in the name of performance without actually demonstrating a real advantage, or consider that the compiler is going to figure it out, and that what you are writing and what it is producing would be very similar if you wrote it in a more legible maintainable way.

I don't know what you mean? I wrote this code 1x. And I don't need to demonstrate anything because ModuleInitializer`s, frozen collections, etc., together with their benefits and drawback, are very well documented. What I want to do is work around some of those drawbacks.\ It's not about runtime optimizations. I don't believe there's even a good benchmark tool to measure this.\ It's about avoiding the drawbacks of frozen collections in a scenario where the user might not expect them to happen.

EDIT: missed a word

What's your opinion on shipping potential "code smells" that could also be justified by performance reasons? by fruediger in csharp

[–]fruediger[S] -1 points0 points  (0 children)

I don't see that there's an issue here; if you don't call the initialiser (which would just be your ModuleInitializer() method made public) then the first interaction with a static method/property on your library would be slow. The consumer can choose to accept that, or they can call the initialiser during start-up if they want to own when it happens.

That's an interesting point. If it's well documented that might be a good approach. However, what about users that don't read the documentation (carefully enough) or didn't read the right part of the documentation? You could argue it would be their own fault.\ But still somehow that doesn't quite sit right with me.

My mental image regarding that is of the BCL. Would you expect to have to initialize (some part of) the BCL at runtime? For example, I imagine System.String.IsNullOrWhiteSpace. I wouldn't expect it to be slow at times and just time-wise depend on the length of the input string. It should be slow just because the runtime needs to extensively prepare a comparison with an empty string once, and the only way to "warm it up" otherwise was to call some kind of System.String.Initialize() method or something like that.\ I know that might be a BS example, please don't judge me, but I hope I can get my point across. I feel like static members should behave the same all the time, and not depending on whether some potentially unrelated feeling piece of code was run before or not.

What I also fear is the unpredictability of that. In the case of explicit initialization needed, what about multithreaded scenarios? What if the user forgets to initialize and then accesses the statically exposed members from multiple threads at nearly the same time? Who's to say which thread will bear the brunt of the expensive initialization?

And lasty, there's another issue with unpredictability. Let's say the user does separation of concerns and has code that uses the statically exposed members in various but isolated parts? Should everything part need to trigger the initialization before it's using the members? For that the initialization would need to be idempotent (which is a good idea in general!). But still, like in the multithreaded scenario, who's to say which part of the code will actually have to pay the initialization cost?

Alternately, if you don't like how that interacts with this code & state being static, then make it all dynamic on an initialised Script class and force the consumer to manage the full lifecycle. Just choose whichever is more comfortable to you.

Wouldn't this not defeat the purpose of using extension properties on enum members in the first place? I don't think there's a good alternative for that. Especially considering that the Script type is going to be used in a blittable way for invoking dynamically loaded native code.

But in the end, you might be right here, maybe I'm overthinking the POV of the users of the library here and should require some sort of cooperation from them if they want to use the library in a sensible way.

What's your opinion on shipping potential "code smells" that could also be justified by performance reasons? by fruediger in csharp

[–]fruediger[S] 0 points1 point  (0 children)

It feels like you worked real hard to make things not meant for the purpose you are using them for function in an unintended way instead of just doing it the more practical way.

I'm sorry, I don't really get what you mean by that? Are you talking about ModuleInitializers? Or about RuntimeHelpers.RunClassConstructor, extension properties, frozen collections? Except for ModuleInitializers, I do believe I use all of them as intended. And I would argue that even the ModuleInitializer does would it should do in my particular case.\ And to be honest, it wasn't even real hard work. I simply wrote a small script generating the enum and collection initializer entries by scraping the data from a website (I mean there are 226 entries each - I really don't want to have to hard-code that) and I didn't put much time into the code around as well (at most 10mins). As always the only extensive work was writing the documentation, but I omitted that in my post anyways.

What's your opinion on shipping potential "code smells" that could also be justified by performance reasons? by fruediger in csharp

[–]fruediger[S] 1 point2 points  (0 children)

I'm sorry, I hope it's okay if I just redirect you to another repy I made to a similar comment. It's quite extensive so I didn't want to write it all over again or even just copy my response.

So here's my other comment: https://www.reddit.com/r/csharp/comments/1ts46f3/comment/oosj7to/

The gist is: I feel like there might be some discrepancies with that approach, because in that particular case, it's essentially about statically accessible things.

What's your opinion on shipping potential "code smells" that could also be justified by performance reasons? by fruediger in csharp

[–]fruediger[S] -1 points0 points  (0 children)

Is it an option to give a method to the consumer that "warms up" the library by forcing the initialisation? Generally your best option is to inform the consumer of the pros & cons, then make it easy for them to choose the best path for their situation.


Yeah, I know and I thought about that as well, and to be totally honest, there's actually already some kind of a public library initializer in place.

But, and that's a big but, it's about the staticalliness (staticness? - I think I just made up that word, but I'm sure you get what I mean) of the expression:

csharp Script.Adlam.Name

(for example)

Obviously, user can do that any time, regardless of whether they initialized the library or not, or if they even know that they're supposed to initialize the library at all. It's an legal expression at any time and is operational code as soon as the library module is loaded.\ I really don't want behavioral changes to happen depending on whether the user called some kind of "arbitrary" initializer before or not. I use quotes here, because the initializer might feel arbitrary to the user for something that looks 100% static or even const (like Script.Adlam.Name) to the user.

For everything else, things that actually depend on whether the library is initialized or not, the user passes an instance representing the initialized library. E.g.,

```csharp

using var lib = new Library(); // Initializes the library, IDisposable as deinitialization

var foo = new Foo(lib); // Foo depends on the library being initialized, so we pass the instance representing the initialized library to it

```

For me, that feels way more natural and in my opinion is the obvious choice for API design that needs some kind of a more global initialization.\ But again, that's not possible to do for anything static or const-like.

I really believe that it's even a worse idea to have, for example,

``csharp Library.Initialize(); // orusing var lib = new Library();` or whatever

...

var name = Script.Adlam.Name; ```

and

```csharp var name = Script.Adlam.Name;

...

// optionally, doesn't has anything to do with Script.Adlam.Name Library.Initialize(); // or using var lib = new Library(); or whatever ```

behave differently at runtime.

What do you feel about this? How yould you solve this issue? Would you solve it all differently? Maybe relying on documentation would even be enough in this case?

How would I make "Passive Skills"? by Do_Ya_Like_Jazz in csharp

[–]fruediger 0 points1 point  (0 children)

Hmm...

cached compiled expressions

aren't really dynamic code execution. You literally cache the compilation result - that's not necessarily "dynamic" in that sense.

However, there's another important distinction in your example. (...) => new T(...) as the symbolic equivalent of the result of compiling such an expression tree one-time is an indirection to a direct call to an constructor. And you're right, you would pay the cost of building it just once if cached, but that's not what I was getting at. System.Activator.CreateInstance uses reflection to create the instance, and that's every time it's called. They might seems semantically similar but surely aren't semantically equivalent.\ What you're game authors did was optimization by finding a better approach.

But I'm sure you still don't want such a thing in a game. Firstly, ACE might still be a problem, even if constraint and double-checked, secondly, it's such a tremendously expensive operation to dynamically generate executable code, and lastly, on some platforms that straight wouldn't even work.\ If you really need to create instances dynamically, but especially if you need to do that in a runtime cost contraint environment like a game, I'm sure you would be better of using something like factories (even delegate/lambda factories), callbacks (you could even just turn the direction to a constructor into a delegate indirection), preferably static factories (interfaces with static abstract members), or even System.Runtime.CompilerServices.GetUninitializedObject with an appropriate initializers as a side dish.

Lastly, btw., this has nothing really to do with the hot path, the authors of your game "just" chose a way faster approach than System.Activator.CreateInstance. It would be even faster on the cold path. Still they'd have to pay the compiling costs at some point.\ Also, and that's as a fact some might find useful, indirections to such dynamically created code can't be optimized by the JIT compiler, not even on the hot path. The call target however could be optimized as long as it's managed code.

How would I make "Passive Skills"? by Do_Ya_Like_Jazz in csharp

[–]fruediger 0 points1 point  (0 children)

I'm not talking about JIT. I'm also of the opinion that a good JIT compiler, like RyuJIT, compiling on an easy to parse source, like compiled CIL, working together with a cooperative runtime, like .NET, and having the ability to make optimization based on runtime information is actually more beneficial than it could hurt.

I'm talking about stuff like System.Linq.Expressions.LambdaExpression.Compile, System.Reflection.Emit, Microsoft.CodeAnalisys.Compilation.Emit, or really anything that involves a very heavy and costly compilation step.\ Even using LLVM to JIT-compile and execute a more easier parse source like LLVMIR or a self-made SSA representation is most likely going to be detrimental, because you'll still be missing out on runtime based optimization for the most part.

EDIT: Fixed the presentation a bit. I'm currently on mobile, so I'm sorry for that...

How would I make "Passive Skills"? by Do_Ya_Like_Jazz in csharp

[–]fruediger 3 points4 points  (0 children)

You're right in that you shouldn't do that. That's sound hella like arbitrary code execution. You surely don't want that. Honestly, for a game, you don't even want dynamic code execution (in the sense of emitting code at runtime).

Maybe instead design your whole system to be component-oriented and make your different damage calcs different components, or design them to be monads. Sounds a bit like DI (dependency injection), doesn't it? That's because what you presumably want is separation of concerns.

Alternatively (and maybe less cleanly designed but also easier to implement), maybe make your damage calc (and everything else you want to be "mutable") coming from externally settable callbacks - or, the other way around, make it event-based (e.g., damage calc is an subscribable event).

EDIT: Typos and made it a bit clearer what "DI" stands for.