dotnet / csharplang

The official repo for the design of the C# programming language
11.58k stars 1.03k forks source link

Champion "Allow no-arg constructor and field initializers in struct declarations" #99

Open gafter opened 7 years ago

gafter commented 7 years ago

See also https://github.com/dotnet/roslyn/issues/13921 https://github.com/dotnet/roslyn/issues/1029

LDM history:

iam3yal commented 7 years ago

@jnm2 I don't think that it's about the benefit but more of a consistency so yes symmetry but dunno what it means for the run-time, I'm not sure what would happen when you define a parameterless constructor and set the fields, I voted because I hope that we could have default values but dunno.

DavidArno commented 7 years ago

Downvoting this as per my comment for Roslyn #13921. For a struct, S, if default(S) and new S() leads to different values, then this feature has no real benefit and will cause serious bugs. This should be implemented properly in the CLR before it's considered for C#.

orthoxerox commented 7 years ago

I agree. If we're going to get non-nullable reference types and structs with parameterless constructors, then we should be able to override default for them,

iam3yal commented 7 years ago

I really hope that they will design and implement this properly.

HaloFour commented 7 years ago

To play Devil's advocate, why is it expected that new struct() and default(struct) produce the same value? That's never been the case with reference types.

Does C# support consuming structs with default constructors today? You could always define one using IL or some other language. How about the IL that C# produces for a generic method with a new constraint? Would that work today with such a type?

The concept of a "default" for struct types is interesting but even in the case of a blittable default you're talking about adding some serious overhead.

iam3yal commented 7 years ago

@HaloFour Well, it might be possible to mitigate the overhead by an attribute? like opt-in/out?

DavidArno commented 7 years ago

@HaloFour,

Your devil's advocate is a good question. To my mind, the answer is "and that is exactly what's wrong with reference types". It is the driver behind non-nullable reference types. SO just at the time when we are looking to address the "null problem", we'd be introducing the same problem in to structs.

HaloFour commented 7 years ago

@eyalsk @DavidArno

I think we're wading into the territory of a different discussion here.

I just tested and answered my questions. C# does currently support consuming structs that define default constructors. It just doesn't support defining them. There is one notable exception and that's in a generic method where the type has a constructor constraint. I believe that this was already considered a bug in the JIT.

gafter commented 7 years ago

We would need the core clr bug https://github.com/dotnet/coreclr/issues/6843 fixed before we could do this.

jnm2 commented 7 years ago

What down-to-earth benefits would we gain from this proposal? I'm inclined to say this raises the complexity of the mental model with no benefits in sight, since no one in Codeplex, /roslyn or this thread has mentioned a single pragmatic benefit despite people repeatedly asking.

iam3yal commented 7 years ago

@jnm2 What do you mean? are you saying that it has zero benefit?

Just as a real world example, I have a text parser where the scanner returns a struct that looks like this:

public TextChar(char value, int index = 0, int lineNumber = 1, int columnNumber = 1)
{
    // ...
}

Now, when you create a new instance of the struct or use default lineNumber and columnNumber should default to 1 when there are no characters at all but atm it default to 0.

Update: Rephrased.

DavidArno commented 7 years ago

@jnm2,

Let's say I have a type that holds information on a car gearbox, including the number of gear stick positions and the gearing ratios and there will always be at least two positions (forward and reverse).

Currently, my options are to use a class (so I can control its construction) and suffer null ref problems, or have a struct. The latter then either suffers the equivalent of the null ref problem if created with default(Gearbox) or new Gearbox(), or I have to leave all the fields as mutable and, on all methods and properties of the struct, I have to put guard code that checks for a misconstruction and sets up a proper default set of values.

If this feature were correctly implemented, eg via an overridable default method in the struct, then I could set those default values (or even throw some "not properly constructed" exception at the point of creation) when default(Gearbox) or new Gearbox() is called.

As the proposal currently stands though, default(Gearbox) wouldn't call the constructor, so from my point of view it doesn't achieve anything useful: I'd still need that guard code or would still need to use a class.

iam3yal commented 7 years ago

BTW, some people workaround this by doing something like the following:

struct S
{
    private int _lineNumber;

    public S(int lineNumber = 1)
    {
        _lineNumber = lineNumber;
    }

    public int LineNumber => _lineNumber == default(int) ? 1 : _lineNumber;
}

Now, this can work but doing this for every field is just boilerplate.

jnm2 commented 7 years ago

@DavidArno default is not a call. Default means zero-fill. Default happens when you create an array. Default happens to all class fields before your initialization code runs. Default happens due to a number of things, and it's for safety so that you don't get junk. Treating it like a call makes no sense because it isn't a call. We need it to be what it is: zero-fill.

paulomorgado commented 7 years ago

I would expect that new S() could be different from default(S).

That means that developers should be aware that their struct can be created without running any constructor.

jnm2 commented 7 years ago

@eyalsk same thing about default. It means "I want this memory to be zero" and changing that semantic would be next to impossible, let alone desirable.

I'm not sure I follow you with the rest of what you showed. Those parameter defaults should only apply if you use that constructor. new TextChar() will not use the constructor because you did not specify the non-optional value, so you would not expect the other parameter defaults to apply since those parameters do not come into play since you chose not to use that constructor.

HaloFour commented 7 years ago

I'd like to reiterate that this proposal has nothing to do with changing how the C# compiler interprets new T() where T is a struct. That behavior is already means something different from default(T) and, as far as I'm aware, that has been the case since C# 1.0. To the CLR, and even to C#, new T() and default(T) have never meant the same thing.

Of course since neither C# nor VB.NET permitted defining a struct with a parameterless constructor the odds of running into one would be exceptionally low. So low that a bug made it into the BCL assuming that such a beast wouldn't exist. But it's quite possible for some other language to define such a struct, and C# already respects such structs by invoking their constructors.

So, in my opinion we need to move on from the conversation about whether structs should have parameterless constructors or whether C# should consider new T() and default(T) to be two different things. That ship has apparently sailed a long time ago. At this point the question should be only whether C# should permit defining new structs with parameterless constructors. Now, if you don't like the idea of parameterless constructors in structs and don't want Microsoft to encourage writing them I definitely think that's a fair argument.

iam3yal commented 7 years ago

@jnm2

I'm not sure I follow you with the rest of what you showed. Those parameter defaults should only apply if you use that constructor. new TextChar() will not use the constructor because you did not specify the non-optional value, so you would not expect the other parameter defaults to apply since those parameters do not come into play since you chose not to use that constructor.

Let's disregard my previous post because it wasn't clear so let's focus on the following examples instead:

public struct Line
{
    public Line(int lineNumber = 1)
    {
        LineNumber = lineNumber;
    }

    public int LineNumber { get; }
}

Possible today but when you do new TextChar() then LineNumber results 0 instead of 1.

public struct Line
{
    private readonly int _lineNumber = 1;

    public Line(int lineNumber)
    {
        _lineNumber = lineNumber;
    }

    public int LineNumber => _lineNumber;
}

Not possible today but I'd like to have it so in the case of the following code:

public struct TextChar
{
    private readonly int _lineNumber = 1;

    private readonly int _columnNumber = 1;

    public TextChar(char value, int index = 0, int lineNumber = 1, int columnNumber = 1)
    {
        Value = value;

        Index = index;

        _lineNumber = lineNumber;

        _columnNumber = columnNumber;
    }

    public int ColumnNumber => _columnNumber;

    public int Index { get; }

    public int LineNumber => _lineNumber;

    public char Value { get; }
}

When I'd do new TextChar() then LineNumber and ColumnNumber would result 1 as opposed to 0.

Now, it's a not a deal breaker but I think that having the ability to do it will fix the slight disparity with classes and people won't need to circumvent or fight the language and do something like this:

public int LineNumber => _lineNumber == default(int) ? 1 : _lineNumber;
DavidArno commented 7 years ago

@HaloFour,

Currently, New S() and default(S) do the same thing in all .NET languages, due to the optimization introduced in Activator.CreateInstance. That comes about because the dominant .NET languages didn't allow custom parameterless constructors, so there was no point in calling it for structs. Because of that, lots of code will have been written that uses default(S) instead of new S(), because they currently do exactly the same thing. As far as I can see therefore, "fixing" that optimisation, and allowing the two struct creation options to behave differently could prove a huge breaking change for many folk.

@jmn2, If non-nullable reference types are introduced, then for a non-nullable class, C, what would you expect the result of default(S) to be? "Zero-filled" null? That seems broken to me as suddenly my non-nullable class as null reference, which just broke my code. Likewise with structs that implement parameterless constructors. Those constructors are being implemented because a zero-filled default doesn't make sense for that type, therefore a zero-filled default(S) would be an invalid value for that struct.

HaloFour commented 7 years ago

@DavidArno

Currently, new S() and default(S) do the same thing in all .NET languages, due to the optimization introduced in Activator.CreateInstance.

That only affects specific uses with generics. When using a struct directly the C# compiler will always invoke the parameterless constructor if it exists. That is also being regarded as a bug in the CLR which at the time of implementation was itself a breaking change.

Even though I do largely use the generic type argument syntax, I am referring to all uses of structs in my argument. If you were handed an assembly the contained a struct called FooStruct and you used new FooStruct() that never implied the same thing as default(FooStruct).

void Test1() {
    var s = new FooStruct(); // invokes parameterless constructor
}

void Test2<T>() where T : struct {
    var s = new T(); // invokes parameterless constructor
}

void Test3<T>() where T : new() {
    var s = new T(); // does not invoke parameterless constructor
}

As mentioned in https://github.com/dotnet/coreclr/issues/6843, structs with parameterless constructors are a part of the CLI spec. As far as I can tell they also meet CLS requirements. C# is required to at least respect them. So is the CLR.

jnm2 commented 7 years ago

@eyalsk So all you are really asking for is struct field initializers. (Field initializers would be possible if you call new Struct() but impossible if you use default(Struct) because, as previously mentioned, default(T) is defined as zeroing the containing memory space.)

I suppose it's a matter of perspective. Since default(Struct) is not going away, nor should it, you'll still have to deal with (new Struct[1])[0] zero-initializing all the fields and you'll still have to harden your struct against zero-inited fields. Since you'll still always have to handle zero-init fields, are field initializers that much of a win? Field initializers can only work if you call a constructor. Creating an array does not call constructors for each element.

iam3yal commented 7 years ago

@jnm2 I can relate to what @DavidArno says but at the same time I wouldn't mind that new S() and default(S) would result a different value.

I wasn't speaking about default and as @HaloFour said we shouldn't derail the discussion as it should be discussed in a new issue so I didn't mention it.

are field initializers that much of a win? Field initializers can only work if you call a constructor. Creating an array does not call constructors for each element.

You're right but at the same time it depends: in my case I'm not returning an array but an Enumerable<TextChar> and all the instance of TextChar are being properly initialized and it's only happens in one place this is the reason I wrote that for me, it isn't a deal breaker, it's just nice to have.

Beyond that I think that it might be nice to fix the slight disparity between classes and structs that might be a surprise to new comers.

jnm2 commented 7 years ago

@eyalsk default is a necessary part of this conversation. I asked what the benefit of this proposal is; you answered that the benefit is not having to "fight the language" by worrying about zero-inited fields.

But because default will happen to every struct, therefore you must harden your field logic against zero-init. Because you must harden your field logic against zero-init, this proposal does not alleviate any worry about zero-inited fields. Therefore, that's not a benefit. Unless some other benefit is proposed, this proposal is at -100. :')

iam3yal commented 7 years ago

@jnm2 I've updated my last post with some more info, I don't know why default is necessary part of this conversation, it's a different thing and as such should be discussed in a new issue.

The discussion about default just "hammer" this proposal down for no reason at all, features should stand on their own and as such discussed separately.

Thaina commented 7 years ago

default and new() for class is difference

So we can assume the same for struct. new Struct() is explicit construct, default(Struct) will be zero memory. And that's normal behaviour

Joe4evr commented 7 years ago

If non-nullable reference types are introduced, then for a non-nullable class, C, what would you expect the result of default(S) to be? "Zero-filled" null? That seems broken to me as suddenly my non-nullable class as null reference, which just broke my code.

I presume you meant to use default(C) there. And well, are you talking about the current thing that really only warns against potential null-derefs at design/compile-time, or are you holding out for some brand new CLR + language version where you could state a class can't be null at its declaration rather than at the individual use-sites?

DavidArno commented 7 years ago

@Joe4evr,

Yes, I did mean default(C). Thanks for the correction.

Like with this feature, only providing compile-time checks would make it a leaky feature, prone to bugs. So yes, I'd want to see it properly implemented in the CLR, not just in the language.

However, it's clear that I'm in the minority here when it comes to structs, so I'm likely in the minority on that feature too. I can't win them all...

Joe4evr commented 7 years ago

Even then, all other languages I heard about that have non-nullable classes have it so you specify whether or not null is an accepted value at the use-site, not at its declaration. And I guess that would be because the latter option would be inhibiting composability.

Thaina commented 7 years ago

@DavidArno If we have non-nullable type. I would prefer that it will be compile time feature you will never be able to create collection of it from new array (at compile time)

But instead you need to convert from another collection (such as by linq) and make sure it will never be null

string![] NonNullable = new string![10]; // ERROR

string![] NonNullable = Enumerable.Repeat((string!)"",10).ToArray(); // Pass

string![] NonNullable = new string![] { "","","","","","","","","","" }; // Pass

I wish all feature like that should only be compile time. We just need to make sure we protect everywhere, such as array of non nullable object like you concern, to avoid bug like that

louthy commented 7 years ago

This is a real pain point when trying to use struct as non-nullable wrappers for more complex types. In my functional library I have a ton of immutable collection types (Lists, Maps, Sets, etc.). They are all structs that contain a single reference to a class with the full implementation. It means I can have:

    Map<string, int> x = default(Map<string, int>); 

But that then requires a null check every time the type is used:

    public struct Map<A, B>
    {
        readonly MapInternal<A, B> value;

        // This property is used throughout the type to get a valid MapInternal
        MapInternal<A, B> Value => value ?? MapInternal<A, B>.Empty;

        // .. impl ...
    }

Obviously this is still better than forcing the programmer to do it manually, but less efficient overall.

I seem to remember this being brought up for C#6 and was dropped, so I'd be interested to know why?

Right now no struct will compile with a default constructor, and existing structs can continue working as they are, with the same semantics for default instantiation and copying. There shouldn't be any need to change any layout behaviours, array layout behaviour, default zeroing behaviour for structs that have no default constructor.

For types with a default constructor then I expect it only to be called when the type is instantiated via new or default(T), any copying semantics should stay the same and not call the default constructor.

jnm2 commented 7 years ago

How would you not have to still check for null? What about default(T) and (new T[1])[0], etc?

You're always going to have to check for null. You would have to check for uninitialization if T was a reference type too, but the runtime does that for you and it does not do that for value types. That's what makes the difference; not whether you can define a default constructor.

louthy commented 7 years ago

@jnm2 The default constructor can set the internal value, that's the point. If default(T) doesn't call the default constructor (or some provided Default override method) then there is literally no point doing this feature in my opinion. There's a massive hole in the type-system which is that certain types can construct without being constructed. I get it for numeric types where blitting the memory with zeros will give you the right result, but for many other things it's problematic. For example I have an Either<L, R> type in my functional library that is a struct, yet I have to rely on having a status field that is an enum with IsUninitialised as the 0 item just in case it gets magicked into existence.

Personally I'd like to see C# take steps to help us get away from garbage undefined types. I understand that default(T) is a problem semantically, but perhaps making it a compile time error for structs of T with default constructors would be OK. But tbh I think it should just call the default constructor, because unlike reference types, default(T) isn't null, default(T) is a fully constructed type with fields, and methods that will appear to work and it's functionally equal to new T() already.

I see all the comments about how difficult this is. But I find excuses like that to be pretty shallow. I can't accept that doing the right thing and making the language more type-safe is ever not worth doing. The two major holes are uninitialised structs and nullable references. I'd be happy to see no other feature for a decade if those two constant thorns were to die.

ufcpp commented 7 years ago

@louthy https://github.com/dotnet/csharplang/issues/146

HaloFour commented 7 years ago

@louthy

This proposal isn't about whether or not structs with default constructors can exist in the CLR type system. They already can, and have been supported since 1.0.

This proposal also isn't about how C# supports those types if it ever comes across them. The language already completely supports invoking those parameterless constructors if they exist and already treats default(T) and new T() as completely separate things when applied to structs.

This proposal is only about whether C# as a language will support letting people define new structs with parameterless constructors. Everything else will remain as it is as any additional changes would break existing behavior.

DavidArno commented 7 years ago

As previously expressed, I completely agree with @louthy that this proposal - allowing new S() and default(S), where S is a struct, to end up as different values - literally has no point. This proposal only makes sense if a means of setting non-zero/null defaults can be achieved via default too.

Not doing this would be the true breaking change as there is likely lots of code out there that currently treats new S() and default(S) as equivalent and this proposal would break those assumptions.

louthy commented 7 years ago

@HaloFour You may have a clearly defined idea of what you think this feature should be. Please don't stop me from expressing what I feel is a major issue with structs and the proposal to leave default(T) behaviour alone.

Adding parameter-less constructors comes with a ton of baggage that should be discussed:

i.e.

    public class Foo
    {
        public Bar X;   // Bar is a struct, does it call new Bar() or default(Bar)
    }

    var x = new Bar [100];   // Does it call new Bar() or default(Bar) foreach item?

If the types (with parameter-less constructors) can still be constructed in a broken state, then there's no point to this feature at all. The point of a parameter-less constructor (in general terms) is to provide a safe default value. The language/CLR ignoring that is insane.

HaloFour commented 7 years ago

@louthy @DavidArno

Not doing this would be the true breaking change as there is likely lots of code out there that currently treats new S() and default(S) as equivalent and this proposal would break those assumptions.

This proposal doesn't change anything about what default(T) and new T() do, that's my point. That decision was made 15 years ago. They've always been different, both for reference and value types. C# has always respected structs with parameterless constructors, it's just never allowed making them. But you could always make them with IL or other languages, and C# would be perfectly happy to invoke that constructor if you used new T().

The rest of the discussion around default(T) is something else entirely. There are and have never been "calls" there. There is only zero-initialization, which is what default(T) means regardless of what T is. This is baked into the CLR. You request an array of size x and the CLR zero-inits a block of memory to fit the elements. It doesn't really care what you're sticking in there or what members that might have (if any).

If you want to argue the semantics of default(T) in C# and the CLR there are other proposals for that. But you're going to have to solve the issues of initialization of unknown/generic types.

orthoxerox commented 7 years ago

@HaloFour

The rest of the discussion around default(T) is something else entirely. There are and have never been "calls" there. There is only zero-initialization, which is what default(T) means regardless of what T is. This is baked into the CLR.

What C# emits for default(T) is not baked into the CLR. It simply chooses to emit something that is baked into the CLR, but could very well emit a call to Activator.CreateDefaultInstance() that would actually check if there was a parameterless constructor and call it.

louthy commented 7 years ago

@HaloFour I appreciate what you're saying, but just as default(T) may have a well defined role in the CLR, it is also a language feature in C#. The language-feature isn't immutable, even if the CLR is. The compiler replacing default(T) with new T() when a struct has a parameter-less constructor isn't going to break any existing C# code (perhaps instantiating types from other languages, but I'm not sure on that, happy to discuss that). It could also see an array allocation and call new T() n times post allocation to populate the array (again, only if the struct has a parameter-less constructor).

It seems to me that giving the programmers of C# the tools to make this decision in favour of type-integrity (over a loss in performance) is essential.

If there's going to be a major problem of types written in F# or VB then perhaps some annotation (attribute or keyword) that allows us to mark a struct as "Yes, I really care about initialising this type", would be the way forward. Either way I shouldn't have to build my own IL to get a safe struct.

HaloFour commented 7 years ago

@orthoxerox

Indeed, you then have to solve that problem across the entirety of the CLR where "default" is assumed to mean zero-init. Generics, arrays, stack allocation, unmanaged code, etc. Good luck with that.

Either way, that's not particularly relevant to whether or not C# allows defining new structs with parameterless constructors. That's a simple CLR-supported and CLS-endorsed language feature.

Thaina commented 7 years ago

The actual case is default(T) is zeromemory. Which always faster than calling constructor. Not to mention your custom constructor

Especially Array of struct. To make a chunk of zeromemory is faster than calling constructor for each one

struct should be fast. So I am very against the idea that calling constructor on struct array

louthy commented 7 years ago

@Thaina This isn't suggesting removing that feature for existing structs. It's only suggesting removing that feature for structs with parameterless constructors (which currently can't be authored in C#). The programmer can decide to provide a parameterless constructor or not, and therefore decide whether to take the performance hit (to benefit from types with integrity) or not. There isn't going to be a general performance hit to any existing structs.

lachbaer commented 7 years ago

To realize this feature without running into the default(Struct) problem, while maintaining wide backwards compatibility I propose the following approach.

(This is no new issue, because it absolutely fits in this proposal)

Forced Nullable Structs

When forcing structs to be nullable-types only, the problem with default(Struct) is actually gone, because that will be null then. During creation, the fields are initialzed and a custom non-args constructor is called.

Syntax

struct? Fraction
{
    public double Numerator;
    public double Denominator = 1.0;
    public Fraction() { Log("..."); }
}

used like

Fraction? frac1;    // is null / default(Fraction?)
Fraction? frac2 = new Fraction();  // initialized
Fraction? frac3 = new Fraction?() // is null / default(Fraction?)
Fraction? frac4 = default(Fraction?) // dto.

Fraction frac5; // error: must be nullable
Fraction frac6 = new Fraction();  // error: must be nullable
Fraction frac7 = default(Fraction) // error: cannot create default for forced nullable

GetUnderlyingType and Value do return the non-nullable type, but only for internal purposes, because explicitly declaring a variable of that underlying non-nullable struct is not possible. The exception is when using reflection or e.g. "violating" calls to Activator.CreateInstance.

Custom conversion operators are possible, but they must respect the structs interal values anyhow to be meaningful.

Internals

Internally the struct is decorated with a ForcedNullableStructAttribute.

[ForcedNullableStruct]
struct Foo { }

For a consistent look-and-feel with Nullables this attribute is attached by declaring the struct with struct?

Calling the constructor is accomplished by (internally) creating a constructor

public Foo(ForcedNullableStructAttribute fnsa)
{
    /* field initialisations and custom non-arg constructor goes here */
    this._isInitialized = true; //see below
}

This constructor is called by the produced MSIL after the instanciation of a struct?. Field initialisations take place at the beginning of the constructor call, like for classes now.

Custom constructors are internally rewritten to

public Foo(double X, double Y) : this( default(ForcedNullableStructAttribute) ) 
{
    // custom code
}

Correct usage of this Forced Nullable Struct is enforced by the compilers. Due to the CLR compatible realisation other .NET languages can make use of the structs, though they must pay attention to the nullable character. Therefore an IsInitialized property is added to every struct?, that is set to true when the initializing constructor is called.

struct? Foo
{
    private readonly bool _isInitialized;
    public bool IsInitialized => _isInitialized;
    / *** /
}

VB Example

Following forced nullable struct is given:

public struct? FNPoint
{
    public double X;
    public double Y;
    public double Z = 1.0;
    public Point(double X, double Y) { this.X = X; this.Y = Y }
    public static string ToString(FNPoint? point)
        => (point.HasValue) ? $"({point?.X}, { point?.Y}, {point?.Z})" : "(null)";
}

In VB.NET this forced nullable struct can also be used, even as a standard-struct.

Dim point = New FNPoint()
Console.WriteLine("Point is initialized: " & point.IsInitialized)
Console.WriteLine($"(X, Y, Z) is {FNPoint.ToString(point)}")
' using it beyond this point is possible, but pointlesss

point = New FNPoint(CType(Nothing, ForcedNullableStructAttribute))
Console.WriteLine("Point is initialized: " & point.IsInitialized)
Console.WriteLine($"(X, Y, Z) is {FNPoint.ToString(point)}")
' the above point can safely be handed around

point = New FNPoint(2.0, 3.0)
Console.WriteLine("Point is initialized: " & point.IsInitialized)
Console.WriteLine($"(X, Y, Z) is {FNPoint.ToString(point)}")
' the above point can safely be handed around

When the point from VB is passed to the static ToString() function, it is implicitly converted to a nullable, because the signature requires so. A signature of ToString(FNPoint point) is not allowed by the compiler for forced nullable structs.

Open questions

DavidArno commented 7 years ago

@lachbaer,

How is that an improvement? You have taken a problem (can't non-zero initialise a struct) and wrapped it in a class and thus introduced null into the mix. Further, as this is just a compiler trick, you now have _isInitialized, that must be checked by each member to ensure correct initialisation when used with other languages. Why would I not just have a struct with _isInitialized, or just make Fraction a class when we get non-nullable references and avoid all this mess?

lachbaer commented 7 years ago

@DavidArno At least it is backwards compatible and practical.

or just make Fraction a class when we get non-nullable references and avoid all this mess

I recommend you study http://stackoverflow.com/questions/85553/when-should-i-use-a-struct-instead-of-a-class 🤣

DavidArno commented 7 years ago

@lachbaer,

Ha, if only the language team had studied that (in particular "Do not define a structure unless the type ... is immutable") when designing value tuples.

For me, the primary reason for considering structs is the fact that they protect code from null. The primary reason for giving up on them 10 seconds later is because I invariably need non-zero initialization of one or more fields (often to avoid null). Your proposal breaks the first consideration and only achieves the second through the need for an initialized-check overhead. So it's not practical from my perspective. It's not so much backwards compatible, as it's a step backwards...

lachbaer commented 7 years ago

@DavidArno Have you seen my comment @ https://github.com/dotnet/csharplang/issues/460#issuecomment-295661489 already?

~My idea here is not a step backwards. It is as legal as any other idea. I even put some effort in it to prove the concept. It's really a bit rough of you to tell it (objectively) a "step backwards" and just giving a discussable argument that is introduced with "For me, the primary reason [...]" That is like slamming me in the face! Do you do that to others participants as well? Sorry, that others don't have an IQ of 180, like you obvious think you have... - I digress. Personal comments should not belong here, just couldn't resist this time~ 😶

One can however argument, that this is too much overhead for a struct and makes it too heavy, as a struct should be lightweight. There are probably only some limited use cases for it. And by creating a pseudo-argument constructor you can achieve nearly the same.

[MyAnalyzer.CallPseudoConstructor]
struct Fraction {
  public double Numerator;
  public double Denominator;
  public Fraction(int Pseudo) {
    Log("Fraction created");
    Numerator = 0.0;
    Denominator = 1.0;
  }
  public Fraction(double num, double, denom) : Fraction(0)
  { Numerator = num; Denominator = denom; }
}

Fraction frac1 = new Fraction(0);
Fraction frac2 = new Fraction(1.0, 1.0);
Fraction frac3; // Analyzer gives error, because no constructor is called
Fraction frac4 = new Fraction();
             // Analyzer gives error, because at least pseude constructor must be called

Together with a new CLR supported type construct "cluct" or "strass" 😁 as initially mentioned in https://github.com/dotnet/csharplang/issues/460#issuecomment-295661489 the requests of this proposal can be served, as well as other issues.

lachbaer commented 7 years ago

@jnm2 Can you explain why you downvoted on the previous post?

jnm2 commented 7 years ago

Can you explain why you downvoted on the previous post?

lachbaer commented 7 years ago

@jnm2 I re-read the objection and I must correct myself.

@DavidArno I am really sorry for the offense! I previously didn't get the meaning of your statement, though reading it several times before I started commenting. I felt it was very offensive to me and my view on things, taken into account other comments to me of yours. This was a misunderstanding because of language misinterpretation (here we go again => human factors 😉 ) Please accept my apologies.