-
Notifications
You must be signed in to change notification settings - Fork 1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Proposal: custom nullable structs #1981
Comments
I don't see how would this proposal be worthwhile, since its only purpose is to make performance optimizations easier. And it doesn't even make them that much easier: Instead of having an public struct Builder
{
private Class _class;
private Class Class => _class ?? (_class = new Class());
public int PropertyOne{get => Class.PropertyOne; set => Class.PropertyOne = value;}
public string PropertyTwo{get => Class.PropertyTwo; set => Class.PropertyTwo = value;}
public Class Build()
{
var @class = _class;
_class = null;
return @class;
}
} |
In that case you've managed to work around it, but it remains that there is no general way to ensure that a struct is always in a valid state. |
@YairHalberstadt What I'm saying is that it's always possible to work around it. And since we're talking about performance-critical code, some verbosity is probably acceptable. |
You can't ensure this for a class either when Reflection and |
These things are on the table to come to all structs anyway: #99. |
That proposal doesn't define a "default value" for a struct. It only allows the default constructor to assign fields of that struct. public struct Foo {
public int Value;
public Foo() {
Value = 123;
}
}
public class Bar {
public Foo X;
public Foo Y = new Foo();
}
var bar = new Bar();
Console.WriteLine(bar.X); // prints 0
Console.WriteLine(bar.Y); // prints 123 |
@Joe4evr |
I took that as exactly what he meant. ¯\_(ツ)_/¯ |
@yaakov We want to make the default thing you try to do correct, not to protect you from doing something. A code analyser is not a general solution, but could work in specific cases |
Maybe I misinterpreted then. I took it to mean that the fields of the struct would always be assigned. |
By default the struct is null, and the field are unassigned. But the struct can't be used unless it is initialised via the constructor, or a null reference exception would be thrown. |
@svick Currently the limitations surrounding structs mean that you only use them when you know you need performance. However, by the time you know you need performance, its often too late, as a little bit of garbage is created in every single part of the program, rather than a single function being the issue. The Idea here is to make structs an almost drop in replacement for classes. You can switch the default from "create a class, unless a struct is necessary" to "create a struct, unless a class is necessary". Currently C++ and Go are struct based OO languages, and they enjoy a significant performance boost as a result. |
Structs will never be a drop-in replacement for classes specifically because the defining characteristic of a struct is their copy-by-value nature, which can quickly and easily eliminate any performance benefit you might get from not allocating on the heap. You can't use them interchangeably in C++ any more than you can in C#. |
@YairHalberstadt, you might only write structs when performance is needed - other developers have other habits. Like me. I write a lot of structs for the clarity that semantic types bring to the code - these are typically immutable value types where a struct provides a simple, clean implementation.
The approach I take to this is ensure the default value for a struct is sensible - either it's a legitimate value, or it will throw if misused. (As an aside - having a separate For the first case, check out this For the later case, the approach I'd take involves a couple of steps. First, define a private
Next, call the method at the start of every public member and you'll soon pick up any context where you're using the struct inappropriately. The Conditional attribute ensures the code is removed from a release build, so you get full performance. |
see also: Roles |
Nullable structs are already a thing? https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/nullable-types |
C++ performance boost has nothing to do with being a struct based language, but the fact that it gives a lot more tools to the dev to allow it to do more work. Even if you used mostly classes in C++, and are a good C++ programmer, the code will be faster than C#, for many other various reasons, you can't limit it to just one, and espically not the it being "struct based" (which I couldn't find that term anywhere, and I've never heard of anyone describe it like that). So if I'm not mistaking, this has nothing to do with nullable types? Because you can do this: struct MyStruct { } and then |
@Willard720 This makes a huge impact on performance. Firstly, short lived objects are almost never created on the heap, but live on the stack. So much so that go doesn't bother with a generational garbage collector, as there's very few gen0 objects to collect. From https://blog.golang.org/ismmkeynote It isn't that the generational hypothesis isn't true for Go, it's just that the young objects live and die young on the stack. The result is that generational collection is much less effective than you might find in other managed runtime languages. Secondly it means heap allocated arrays of objects have much better data locality. See http://joeduffyblog.com/2010/10/31/dense-and-pointerfree/ See also this quote from http://joeduffyblog.com/2010/09/06/the-premature-optimization-is-evil-myth/ If I could do it all over again, I would make some changes to C#. I would try to keep pointers, and merely not allow you to free them. Indirections would be explicit. The reference type versus value type distinction would not exist; whether something was a reference would work like C++, i.e. you get to decide. Things get tricky when you start allocating things on the stack, because of the lifetime issues, so we’d probably only support stack allocation for a subset of primitive types. (Or we’d employ conservative escape analysis.) Anyway, the point here is to illustrate that in such a world, you’d be more conscious about how data is laid out in memory, encouraging dense representations over sparse and pointer rich data structures silently spreading all over the place. We don’t live in this world, so pretend as though we do; each time you see a reference to an object, think “indirection!” to yourself and react as you would in C++ when you see a pointer dereference. |
Thanks @HaloFour for those links. Very interesting. Nonetheless, that does not invalidate the advantages of creating structs on the stack at compile time, compared to creating objects which may possibly be stack allocated by the JIT if the analysis is simple enough to do so. |
Nothing prevents you from doing so. But you inherit with that all of the disadvantages of structs, including no default value, no mechanism to enforce initialization, limited lifetime, copy semantics and very strict reference semantics. Structs aren't some magic silver bullet. They can't replace classes in the majority of cases simply because of the lifetime requirements. Even in the blog post you cited stack allocation was called out as being tricky, to the extend that his reimagined C# wouldn't allow it at all except for primitive types or escape analysis. |
Closed in favour of #2019 |
Motivation
Structs are often more performant then classes, as they improve data locality, and reduce stress on the garbage collector. Indeed in high performance code it is advised to use structs wherever possible.
In general I try to replace classes with structs, if the struct would be small enough that copying wont be an issue.
The biggest issue with doing so is that you cannot define, or prevent the default constructor. Neither can you provide default values for field or properties. As such it is impossible to make sure that a struct is initialised to a valid value before it is used, which is a major limitation for many of the cases where I try to replace a class with a struct. Indeed I would say it is the single most common reason why I do not replace a class with a struct.
There is a good reason for this. When default(T) is called, where T is a type of struct, a zeroed out struct is allocated. This is necessary for performance reasons, as the garbage collector automatically zeroes out segments it acquires.
It would be very strange for default(T) to return a valid struct which is different to the struct returned by a call to new().
Proposed Solution
This isn't an issue for classes, since a call to default(T) where T is a class returns null, and it is understood that you have to check a class for null before accessing it.
We also have the Nullable struct type, where you have to check if they are valid before using them. However you can't specify that a given struct type should be nullable.
So I propose we allow defining custom nullable structs with the following feature:
For a custom nullable struct type T
Suggested Syntax and Example
Motivating use case
As just one example of a real life use case, consider this code I was trying to write:
The problem with this pattern is that you are required to Initialise the Builder before usage, which is prone to errors. This also means you can't use object initialisers with the builder.
The alternative would be using a class, but this adds a new object to the heap for no reason, and doubles the cost of creating a new Class.
With this proposal we could write:
semantics relating to nullable reference types and Nullable
#1865 discusses adding nullable reference type features to nullable value types. Presumable similiar features called be added to custom nullable value types.
Open question would include whether you could refer to the custom nullable type without the
?
ending when you know the value is not null.So for example in the example given above could you declare a
new Class.Builder()
or only anew Class.Builder?()
. Could you sayClass.Builder builder = new Class.Builder?()
or onlyClass.Builder? builder = new Class.Builder?()
.The text was updated successfully, but these errors were encountered: