5 ms·
As a Java-lander, programming C# for fun, while I like struct as a concept they are horrible in practice. What do I mean by that? Here's my idea. Make a Fluent
by Ygg2 6y ago
As a Java-lander, programming C# for fun, while I like struct as a concept they are horrible in practice.
What do I mean by that? Here's my idea. Make a Fluent parser. I have some few shallow structs that can be used interchangeably. E.g. you want to have following structure
Entry:
- Message
- Term
- Comment
How do I add polymorphism to my entries?
Option 1) Go with interfaces. Yay. I've incurred the boxing penalty. I might as well as just write a class.
Option 2) Go with [FieldOffset]. Only do this if you are a masochist and you REEEEEEEEEEEEAAAAAAAALLY need that performance.
In Rust for instance, I could replace that stupid struct with an enumeration, which is impossible in C#. Hopefully in future there will be discriminated unions or some Rust enums equivalent.
- virgilp 6y agoThis seems quite a leap from "C# doesn't (yet) have union types" to "structs are horrible in practice". Not all usecases require union types.
- Ygg2 6y agoStructs are often horrible in practice if you need even a bit of polymorphism. If C# had the ability to define List< struct A | B | C > I would been singing a different tune. You either in for List<IInterface> or List<ABCFieldOffset>
- zigzag312 6y agoThis would be beautiful
- csharptwdec19 6y agoThere's a few challenges with doing something like this. - arrays (which most collections use under the covers) will need to allocate `elements*size_of_struct`. Unless the structs are the same size, you'd have to do some level of thunking/offsetting. - Even if they are the same size, when you're iterating the list, how would the runtime know whether it's `A`, `B`, or `C`? Structs don't have header information that can be used to infer the type, it's only whatever fields you've defined (with padding potentially). Part of 'boxing' is essentially thunking the struct by wrapping a methodtable for the struct into the object [0]. If you need something like polymorphism with structs, your best bet is to make the struct itself quasi-polymorphic (i.e. with enums) and/or start abusing generics in some fashion I haven't yet figured out. [0] - https://mattwarren.org/2017/08/02/A-look-at-the-internals-of-boxing-in-the-CLR/ https://mattwarren.org/2017/08/02/A-look-at-the-internals-of...
- Ygg2 6y ago> Structs don't have header information that can be used to infer the type, it's only whatever fields you've defined (with padding potentially). Sure, so let's make union or "struct enum" and do the same thing Rust does. Add a discriminator byte to the structure and use it to figure out type. > If you need something like polymorphism with structs, your best bet is to make the struct itself quasi-polymorphic (i.e. with enums) and/or start abusing generics in some fashion I haven't yet figured out. Well. I think there is the FieldOffset hack. Basically discriminated unions where programmer plays the role of compiler. [StructLayout(LayoutKind.Explicit)] struct DiscriminatedUnion { // Imagine we have enum like so // enum // { // V1(byte a, byte b) // V2(sbyte a, sbyte b) // } // The "FieldOffset" means that this Integer starts, an offset in bytes. // sizeof(byte) = 1, sizeof(EnumType) = 1 [FieldOffset(0)] public EnumType TypeFlag; [FieldOffset(1)] public byte V1a; [FieldOffset(2)] public byte V1b; [FieldOffset(1)] public sbyte V2a; [FieldOffset(2)] public sbyte V2b; } https://sodocumentation.net/csharp/topic/5626/how-to-use-csharp-structs-to-create-a-union-type---similar-to-c-unions- https://sodocumentation.net/csharp/topic/5626/how-to-use-csh... Problem is, this is fragile. It might break on endianess.
- alkonaut 6y ago> Structs are often horrible in practice if you need even a bit of polymorphism I think that's what he is saying. That it's quite a leap from "structs are horrible in practice" to "structs are horrible if you need polymorphism in conjuction with structs" which is in my experience a pretty rare situation. If you approach structs from Rust or C++ you might expect to be able to form hierarchies of them, but approaching it from Java you are very happy to just be able to create a Point(X, Y) struct. Even the rust type of enum structs with clever overlapping layouts I find pretty rare use for. The common use case is homogeneous data. If I have polymorphism, I'm likely going to incur some pretty expensive virtcalls etc anyway. At that point I just use classes.
- gpderetta 6y agoAs a C++-lander, the fact that the struct/class dichotomy dictates boxing and polymorphism is mind boggling.
- josefx 6y agoIn C++ struct and class are the same except for default visibility rules. You could drop either keyword from the language and it wouldn't make any practical difference outside of C compatibility. In GCed languages the default seems to be that every object is referred to by pointer. Java took the "ugly" step of adding in primitive types so you could calculate 1 + 1 without causing dozens of cache misses. Struct types take this hack a step further. It generally makes the definition of the languages virtual machine a lot uglier, for example most jvm instructions operate on primitive types.
- Ygg2 6y agoWell I understand why it's there. Structs are just that data stored in the same layout. Classes are just a vtable pointer to some object on heap.
- mjburgess 6y agoIt's because "class" and "class" are referring to two difference semantics. An OO GC'd language "class" is a mechanism for run-time polymorphism. That's what it's for. C++ has no "runtime" concerns of these kinds, so virtualization sits on top systems with compile-time semantics (ie., c++ classes).
- 6y ago
- dfgdghdf 6y agoYou are writing C# like it's F#. In F# you can write union types that are also structs: [<Struct>] type Entry = | Message | Term | Comment And like in Rust, you have pattern matching on them: let computeFooForEntry entry = match entry with | Message -> 0 | Term -> 1 | Comment -> 2
- Ygg2 6y agoI'm not trying to. It's just a shallow type hierarchy. And I can achieve it with Interface. But I would prefer a solution that doesn't box.
- dfgdghdf 6y agoInterfaces don't have a compile-time size so I don't see how you can use avoid boxing if you want a collection of IFoo. (Optimized) discriminated unions have a size that is the max size of the possible types, plus a flag to indicate the type. This is the best you can hope for without boxing.
- Const-me 6y agoInterfaces don’t always have boxing penalty. Write generic code, use the interface as generic type constraint, and the interface will work without virtual calls, boxing, or any other runtime overhead. Example of that approach: https://github.com/Const-me/Vrmac/blob/1.2/Vrmac/Draw/Main/ImmediateContext.upload.cs#L86-L137 https://github.com/Const-me/Vrmac/blob/1.2/Vrmac/Draw/Main/I...