I think this is a compiler bug.
The following console application compiles und executes flawlessly when compiled with VS 2015:
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
var x = MyStruct.Empty;
}
public struct MyStruct
{
public static readonly MyStruct Empty = new MyStruct();
}
}
}
But now it's getting weird: This code compiles, but it throws a TypeLoadException when executed.
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
var x = MyStruct.Empty;
}
public struct MyStruct
{
public static readonly MyStruct? Empty = null;
}
}
}
Do you experience the same issue? If so, I will file an issue at Microsoft.
The code looks senseless, but I use it to improve readability and to achieve disambiguation.
I have methods with different overloads like
void DoSomething(MyStruct? arg1, string arg2)
void DoSomething(string arg1, string arg2)
Calling a method this way...
myInstance.DoSomething(null, "Hello world!")
... does not compile.
Calling
myInstance.DoSomething(default(MyStruct?), "Hello world!")
or
myInstance.DoSomething((MyStruct?)null, "Hello world!")
works, but looks ugly. I prefer it this way:
myInstance.DoSomething(MyStruct.Empty, "Hello world!")
If I put the Empty variable into another class, everything works okay:
public static class MyUtility
{
public static readonly MyStruct? Empty = null;
}
Strange behavior, isn't it?
UPDATE 2016-03-29
I opened a ticket here: http://github.com/dotnet/roslyn/issues/10126
UPDATE 2016-04-06
A new ticket has been opened here: https://github.com/dotnet/coreclr/issues/4049
First off, it is important when analyzing these issues to make a minimal reproducer, so that we can narrow down where the problem is. In the original code there are three red herrings: the readonly, the static and the Nullable<T>. None are necessary to repro the issue. Here's a minimal repro:
struct N<T> {}
struct M { public N<M> E; }
class P { static void Main() { var x = default(M); } }
This compiles in the current version of VS, but throws a type load exception when run.
The exception is not triggered by use of E. It is triggered by any attempt to access the type M. (As one would expect in the case of a type load exception.)
The exception reproduces whether the field is static or instance, readonly or not; this has nothing to do with the nature of the field. (However it must be a field! The issue does not repro if it is, say, a method.)
The exception has nothing whatsoever to do with "invocation"; nothing is being "invoked" in the minimal repro.
The exception has nothing whatsoever to do with the member access operator ".". It does not appear in the minimal repro.
The exception has nothing whatsoever to do with nullables; nothing is nullable in the minimal repro.
Now let's do some more experiments. What if we make N and M classes? I will tell you the results:
The behaviour only reproduces when both are structs.
We could go on to discuss whether the issue reproduces only when M in some sense "directly" mentions itself, or whether an "indirect" cycle also reproduces the bug. (The latter is true.) And as Corey notes in his answer, we could also ask "do the types have to be generic?" No; there is a reproducer even more minimal than this one with no generics.
However I think we have enough to complete our discussion of the reproducer and move on to the question at hand, which is "is it a bug, and if so, in what?"
Plainly something is messed up here, and I lack the time today to sort out where the blame ought to fall. Here are some thoughts:
The rule against structs containing members of themselves plainly does not apply here. (See section 11.3.1 of the C# 5 specification, which is the one I have present at hand. I note that this section could benefit from a careful rewriting with generics in mind; some of the language here is a bit imprecise.) If E is static then that section does not apply; if it is not static then the layouts of N<M> and M can both be computed regardless.
I know of no other rule in the C# language that would prohibit this arrangement of types.
It might be the case that the CLR specification prohibits this arrangement of types, and the CLR is right to throw an exception here.
So now let us sum up the possibilities:
The CLR has a bug. This type topology should be legal, and it is wrong of the CLR to throw here.
The CLR behaviour is correct. This type topology is illegal, and it is correct of the CLR to throw here. (In this scenario it may be the case that the CLR has a spec bug, in that this fact may not be adequately explained in the specification. I don't have time to do CLR spec diving today.)
Let us suppose for the sake of argument that the second is true. What can we now say about C#? Some possibilities:
The C# language specification prohibits this program, but the implementation allows it. The implementation has a bug. (I believe this scenario to be false.)
The C# language specification does not prohibit this program, but it could be made to do so at a reasonable implementation cost. In this scenario the C# specification is at fault, it should be fixed, and the implementation should be fixed to match.
The C# language specification does not prohibit the program, but detecting the problem at compile time cannot be done at reasonable cost. This is the case with pretty much any runtime crash; your program crashed at runtime because the compiler couldn't stop you from writing a buggy program. This is just one more buggy program; unfortunately, you had no reason to know it was buggy.
Summing up, our possibilities are:
The CLR has a bug
The C# spec has a bug
The C# implementation has a bug
The program has a bug
One of these four must be true. I do not know which it is. Were I asked to guess, I'd pick the first one; I see no reason why the CLR type loader ought to balk on this one. But perhaps there is a good reason that I do not know; hopefully an expert on the CLR type loading semantics will chime in.
UPDATE:
This issue is tracked here:
https://github.com/dotnet/roslyn/issues/10126
To sum up the conclusions from the C# team in that issue:
The program is legal according to both the CLI and C# specifications.
The C# 6 compiler allows the program, but some implementations of the CLI throw a type load exception. This is a bug in those implementations.
The CLR team is aware of the bug, and apparently it is hard to fix on the buggy implementations.
The C# team is considering making the legal code produce a warning, since it will fail at runtime on some, but not all, versions of the CLI.
The C# and CLR teams are on this; follow up with them. If you have any more concerns with this issue please post to the tracking issue, not here.
This is not a bug in 2015 but a possibly a C# language bug. The discussion below relates to why instance members cannot introduce loops, and why a Nullable<T> will cause this error, but should not apply to static members.
I would submit it as a language bug, not a compiler bug.
Compiling this code in VS2013 gives the following compile error:
Struct member 'ConsoleApplication1.Program.MyStruct.Empty' of type 'System.Nullable' causes a cycle in the struct layout
A quick search turns up this answer which states:
It's not legal to have a struct that contains itself as a member.
Unfortunately the System.Nullable<T> type which is used for nullable instances of value types is also a value type and must therefore have a fixed size. It's tempting to think of MyStruct? as a reference type, but it really isn't. The size of MyStruct? is based on the size of MyStruct... which apparently introduces a loop in the compiler.
Take for instance:
public struct Struct1
{
public int a;
public int b;
public int c;
}
public struct Struct2
{
public Struct1? s;
}
Using System.Runtime.InteropServices.Marshal.SizeOf() you'll find that Struct2 is 16 bytes long, indicating that Struct1? is not a reference but a struct that is 4 bytes (standard padding size) longer than Struct1.
What's not happening here
In response to Julius Depulla's answer and comments, here is what is actually happening when you access a static Nullable<T> field. From this code:
public struct foo
{
public static int? Empty = null;
}
public void Main()
{
Console.WriteLine(foo.Empty == null);
}
Here is the generated IL from LINQPad:
IL_0000: ldsflda UserQuery+foo.Empty
IL_0005: call System.Nullable<System.Int32>.get_HasValue
IL_000A: ldc.i4.0
IL_000B: ceq
IL_000D: call System.Console.WriteLine
IL_0012: ret
The first instruction gets the address of the static field foo.Empty and pushes it on the stack. This address is guaranteed to be non-null as Nullable<Int32> is a structure and not a reference type.
Next the Nullable<Int32> hidden member function get_HasValue is called to retrieve the HasValue property value. This cannot result in a null reference since, as mentioned previously, the address of a value type field must be non-null, regardless of the value contained at the address.
The rest is just comparing the result to 0 and sending the result to the console.
At no point in this process is it possible to 'invoke a null on a type' whatever that means. Value types do not have null addresses, so method invocation on value types cannot directly result in a null object reference error. That's why we don't call them reference types.
Now that we've had a lengthy discussion about what and why, here's a way to work around the issue without having to wait on the various .NET teams to track down the issue and determine what if anything will be done about it.
The issue appears to be restricted to field types that are value types which reference back to this type in some way, either as generic parameters or static members. For instance:
public struct A { public static B b; }
public struct B { public static A a; }
Ugh, I feel dirty now. Bad OOP, but it demonstrates that the problem exists without invoking generics in any way.
So because they are value types the type loader determines that there is a circularity involved that should be ignored because of the static keyword. The C# compiler was smart enough to figure it out. Whether it should have or not is up to the specs, on which I have no comment.
However, by changing either A or B to class the problem evaporates:
public struct A { public static B b; }
public class B { public static A a; }
So the problem can be avoided by using a reference type to store the actual value and convert the field to a property:
public struct MyStruct
{
private static class _internal { public static MyStruct? empty = null; }
public static MyStruct? Empty => _internal.empty;
}
This is a bunch slower because it's a property instead of a field and calls to it will invoke the get method, so I wouldn't use it for performance-critical code, but as a workaround it at least lets you do the job until a proper solution is available.
And if it turns out that this doesn't get resolved, at least we have a kludge we can use to bypass it.
Related
Apologies if this is a duplicate! I searched around but wasn't able to find an explanation. The following toy example gives me a TypeLoadException as soon as I try to instantiate this struct. It works just fine if I use a class or if instead do not specify the generic type in the static member (leave it as T.)
public struct Point<T>
{
static Point<int> IntOrigin = new Point<int>(0, 0);
T X { get; }
T Y { get; }
public Point(T x, T y)
{
this.X = x;
this.Y = y;
}
}
My rather more complicated, real situation boils down to something like this, so I'd really like to understand why it emits a TypeLoadException.
This comment, and another comment, on Github come closest to addressing the current state of affairs, pointing out why the sort of self-referential struct definition hasn't been allowed, and likely won't be for the foreseeable future.
Even the static member needs the type to be initialized before it can be included in the type layout, but the type initialization needs that static member to be initialized. This cycle of initialization dependencies creates the Catch-22 that results in the runtime exception.
According to this comment, .NET Core works fine with this pattern. But I found the same failure when I tried your example in a .NET Core project. So either that comment is in error, or there's some subtle difference between the instance-member scenario and your static-member scenario (I didn't bother to investigate any further than that).
It is interesting that the compiler used on dotNETFiddle.net emits a compile-time error, "Struct member 'struct2 field' of type 'struct1' causes a cycle in the struct layout". I don't know why the Visual Studio compiler no longer seems to produce this error (checked in 2017 and 2019). It seems like another bug to me. But the discussion on Github around this issue seems to accept that the code is technically valid (i.e. according to the C# specification), so probably there was at some point a conscious decision to remove the compiler error, and let the CLR do the complaining at runtime.
Note that the advice in the error's reference page suggests changing to a class instead of a struct. Of course, this is often not feasible where a struct is being used; it may be important to have a value type. However, in your specific example, there's actually a simple work-around based on that idea. Since your field isn't part of the actual layout of an instance of the struct, you can move it to a static class used specifically for such values. E.g.:
public struct Point<T>
{
public static class Constants
{
static Point<int> IntOrigin = new Point<int>(0, 0);
}
T X { get; }
T Y { get; }
public Point(T x, T y)
{
this.X = x;
this.Y = y;
}
}
Then instead of (for example) Point<double>.IntOrigin, you'll need to use Point<double>.Constants.IntOrigin. Since the type initialization for each type can be done independently, the cycle in the initialization does not occur and the code runs fine.
I think this is a compiler bug.
The following console application compiles und executes flawlessly when compiled with VS 2015:
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
var x = MyStruct.Empty;
}
public struct MyStruct
{
public static readonly MyStruct Empty = new MyStruct();
}
}
}
But now it's getting weird: This code compiles, but it throws a TypeLoadException when executed.
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
var x = MyStruct.Empty;
}
public struct MyStruct
{
public static readonly MyStruct? Empty = null;
}
}
}
Do you experience the same issue? If so, I will file an issue at Microsoft.
The code looks senseless, but I use it to improve readability and to achieve disambiguation.
I have methods with different overloads like
void DoSomething(MyStruct? arg1, string arg2)
void DoSomething(string arg1, string arg2)
Calling a method this way...
myInstance.DoSomething(null, "Hello world!")
... does not compile.
Calling
myInstance.DoSomething(default(MyStruct?), "Hello world!")
or
myInstance.DoSomething((MyStruct?)null, "Hello world!")
works, but looks ugly. I prefer it this way:
myInstance.DoSomething(MyStruct.Empty, "Hello world!")
If I put the Empty variable into another class, everything works okay:
public static class MyUtility
{
public static readonly MyStruct? Empty = null;
}
Strange behavior, isn't it?
UPDATE 2016-03-29
I opened a ticket here: http://github.com/dotnet/roslyn/issues/10126
UPDATE 2016-04-06
A new ticket has been opened here: https://github.com/dotnet/coreclr/issues/4049
First off, it is important when analyzing these issues to make a minimal reproducer, so that we can narrow down where the problem is. In the original code there are three red herrings: the readonly, the static and the Nullable<T>. None are necessary to repro the issue. Here's a minimal repro:
struct N<T> {}
struct M { public N<M> E; }
class P { static void Main() { var x = default(M); } }
This compiles in the current version of VS, but throws a type load exception when run.
The exception is not triggered by use of E. It is triggered by any attempt to access the type M. (As one would expect in the case of a type load exception.)
The exception reproduces whether the field is static or instance, readonly or not; this has nothing to do with the nature of the field. (However it must be a field! The issue does not repro if it is, say, a method.)
The exception has nothing whatsoever to do with "invocation"; nothing is being "invoked" in the minimal repro.
The exception has nothing whatsoever to do with the member access operator ".". It does not appear in the minimal repro.
The exception has nothing whatsoever to do with nullables; nothing is nullable in the minimal repro.
Now let's do some more experiments. What if we make N and M classes? I will tell you the results:
The behaviour only reproduces when both are structs.
We could go on to discuss whether the issue reproduces only when M in some sense "directly" mentions itself, or whether an "indirect" cycle also reproduces the bug. (The latter is true.) And as Corey notes in his answer, we could also ask "do the types have to be generic?" No; there is a reproducer even more minimal than this one with no generics.
However I think we have enough to complete our discussion of the reproducer and move on to the question at hand, which is "is it a bug, and if so, in what?"
Plainly something is messed up here, and I lack the time today to sort out where the blame ought to fall. Here are some thoughts:
The rule against structs containing members of themselves plainly does not apply here. (See section 11.3.1 of the C# 5 specification, which is the one I have present at hand. I note that this section could benefit from a careful rewriting with generics in mind; some of the language here is a bit imprecise.) If E is static then that section does not apply; if it is not static then the layouts of N<M> and M can both be computed regardless.
I know of no other rule in the C# language that would prohibit this arrangement of types.
It might be the case that the CLR specification prohibits this arrangement of types, and the CLR is right to throw an exception here.
So now let us sum up the possibilities:
The CLR has a bug. This type topology should be legal, and it is wrong of the CLR to throw here.
The CLR behaviour is correct. This type topology is illegal, and it is correct of the CLR to throw here. (In this scenario it may be the case that the CLR has a spec bug, in that this fact may not be adequately explained in the specification. I don't have time to do CLR spec diving today.)
Let us suppose for the sake of argument that the second is true. What can we now say about C#? Some possibilities:
The C# language specification prohibits this program, but the implementation allows it. The implementation has a bug. (I believe this scenario to be false.)
The C# language specification does not prohibit this program, but it could be made to do so at a reasonable implementation cost. In this scenario the C# specification is at fault, it should be fixed, and the implementation should be fixed to match.
The C# language specification does not prohibit the program, but detecting the problem at compile time cannot be done at reasonable cost. This is the case with pretty much any runtime crash; your program crashed at runtime because the compiler couldn't stop you from writing a buggy program. This is just one more buggy program; unfortunately, you had no reason to know it was buggy.
Summing up, our possibilities are:
The CLR has a bug
The C# spec has a bug
The C# implementation has a bug
The program has a bug
One of these four must be true. I do not know which it is. Were I asked to guess, I'd pick the first one; I see no reason why the CLR type loader ought to balk on this one. But perhaps there is a good reason that I do not know; hopefully an expert on the CLR type loading semantics will chime in.
UPDATE:
This issue is tracked here:
https://github.com/dotnet/roslyn/issues/10126
To sum up the conclusions from the C# team in that issue:
The program is legal according to both the CLI and C# specifications.
The C# 6 compiler allows the program, but some implementations of the CLI throw a type load exception. This is a bug in those implementations.
The CLR team is aware of the bug, and apparently it is hard to fix on the buggy implementations.
The C# team is considering making the legal code produce a warning, since it will fail at runtime on some, but not all, versions of the CLI.
The C# and CLR teams are on this; follow up with them. If you have any more concerns with this issue please post to the tracking issue, not here.
This is not a bug in 2015 but a possibly a C# language bug. The discussion below relates to why instance members cannot introduce loops, and why a Nullable<T> will cause this error, but should not apply to static members.
I would submit it as a language bug, not a compiler bug.
Compiling this code in VS2013 gives the following compile error:
Struct member 'ConsoleApplication1.Program.MyStruct.Empty' of type 'System.Nullable' causes a cycle in the struct layout
A quick search turns up this answer which states:
It's not legal to have a struct that contains itself as a member.
Unfortunately the System.Nullable<T> type which is used for nullable instances of value types is also a value type and must therefore have a fixed size. It's tempting to think of MyStruct? as a reference type, but it really isn't. The size of MyStruct? is based on the size of MyStruct... which apparently introduces a loop in the compiler.
Take for instance:
public struct Struct1
{
public int a;
public int b;
public int c;
}
public struct Struct2
{
public Struct1? s;
}
Using System.Runtime.InteropServices.Marshal.SizeOf() you'll find that Struct2 is 16 bytes long, indicating that Struct1? is not a reference but a struct that is 4 bytes (standard padding size) longer than Struct1.
What's not happening here
In response to Julius Depulla's answer and comments, here is what is actually happening when you access a static Nullable<T> field. From this code:
public struct foo
{
public static int? Empty = null;
}
public void Main()
{
Console.WriteLine(foo.Empty == null);
}
Here is the generated IL from LINQPad:
IL_0000: ldsflda UserQuery+foo.Empty
IL_0005: call System.Nullable<System.Int32>.get_HasValue
IL_000A: ldc.i4.0
IL_000B: ceq
IL_000D: call System.Console.WriteLine
IL_0012: ret
The first instruction gets the address of the static field foo.Empty and pushes it on the stack. This address is guaranteed to be non-null as Nullable<Int32> is a structure and not a reference type.
Next the Nullable<Int32> hidden member function get_HasValue is called to retrieve the HasValue property value. This cannot result in a null reference since, as mentioned previously, the address of a value type field must be non-null, regardless of the value contained at the address.
The rest is just comparing the result to 0 and sending the result to the console.
At no point in this process is it possible to 'invoke a null on a type' whatever that means. Value types do not have null addresses, so method invocation on value types cannot directly result in a null object reference error. That's why we don't call them reference types.
Now that we've had a lengthy discussion about what and why, here's a way to work around the issue without having to wait on the various .NET teams to track down the issue and determine what if anything will be done about it.
The issue appears to be restricted to field types that are value types which reference back to this type in some way, either as generic parameters or static members. For instance:
public struct A { public static B b; }
public struct B { public static A a; }
Ugh, I feel dirty now. Bad OOP, but it demonstrates that the problem exists without invoking generics in any way.
So because they are value types the type loader determines that there is a circularity involved that should be ignored because of the static keyword. The C# compiler was smart enough to figure it out. Whether it should have or not is up to the specs, on which I have no comment.
However, by changing either A or B to class the problem evaporates:
public struct A { public static B b; }
public class B { public static A a; }
So the problem can be avoided by using a reference type to store the actual value and convert the field to a property:
public struct MyStruct
{
private static class _internal { public static MyStruct? empty = null; }
public static MyStruct? Empty => _internal.empty;
}
This is a bunch slower because it's a property instead of a field and calls to it will invoke the get method, so I wouldn't use it for performance-critical code, but as a workaround it at least lets you do the job until a proper solution is available.
And if it turns out that this doesn't get resolved, at least we have a kludge we can use to bypass it.
I had thought that Generics in C# were implemented such that a new class/method/what-have-you was generated, either at run-time or compile-time, when a new generic type was used, similar to C++ templates (which I've never actually looked into and I very well could be wrong, about which I'd gladly accept correction).
But in my coding I came up with an exact counterexample:
static class Program {
static void Main()
{
Test testVar = new Test();
GenericTest<Test> genericTest = new GenericTest<Test>();
int gen = genericTest.Get(testVar);
RegularTest regTest = new RegularTest();
int reg = regTest.Get(testVar);
if (gen == ((object)testVar).GetHashCode())
{
Console.WriteLine("Got Object's hashcode from GenericTest!");
}
if (reg == testVar.GetHashCode())
{
Console.WriteLine("Got Test's hashcode from RegularTest!");
}
}
class Test
{
public new int GetHashCode()
{
return 0;
}
}
class GenericTest<T>
{
public int Get(T obj)
{
return obj.GetHashCode();
}
}
class RegularTest
{
public int Get(Test obj)
{
return obj.GetHashCode();
}
}
}
Both of those console lines print.
I know that the actual reason this happens is that the virtual call to Object.GetHashCode() doesn't resolve to Test.GetHashCode() because the method in Test is marked as new rather than override. Therefore, I know if I used "override" rather than "new" on Test.GetHashCode() then the return of 0 would polymorphically override the method GetHashCode in object and this wouldn't be true, but according to my (previous) understanding of C# generics it wouldn't have mattered because every instance of T would have been replaced with Test, and thus the method call would have statically (or at generic resolution time) been resolved to the "new" method.
So my question is this: How are generics implemented in C#? I don't know CIL bytecode, but I do know Java bytecode so I understand how Object-oriented CLI languages work at a low level. Feel free to explain at that level.
As an aside, I thought C# generics were implemented that way because everyone always calls the generic system in C# "True Generics," compared to the type-erasure system of Java.
In GenericTest<T>.Get(T), the C# compiler has already picked that object.GetHashCode should be called (virtually). There's no way this will resolve to the "new" GetHashCode method at runtime (which will have its own slot in the method-table, rather than overriding the slot for object.GetHashCode).
From Eric Lippert's What's the difference, part one: Generics are not templates, the issue is explained (the setup used is slightly different, but the lessons translate well to your scenario):
This illustrates that generics in C# are not like templates in C++.
You can think of templates as a fancy-pants search-and-replace
mechanism.[...] That’s not how generic types work; generic types are,
well, generic. We do the overload resolution once and bake in the
result. [...] The IL we’ve generated for the generic type already has
the method its going to call picked out. The jitter does not say
“well, I happen to know that if we asked the C# compiler to execute
right now with this additional information then it would have picked a
different overload. Let me rewrite the generated code to ignore the
code that the C# compiler originally generated...” The jitter knows
nothing about the rules of C#.
And a workaround for your desired semantics:
Now, if you do want overload resolution to be re-executed at runtime based on the runtime types of
the arguments, we can do that for you; that’s what the new “dynamic”
feature does in C# 4.0. Just replace “object” with “dynamic” and when
you make a call involving that object, we’ll run the overload
resolution algorithm at runtime and dynamically spit code that calls
the method that the compiler would have picked, had it known all the
runtime types at compile time.
I've been feeling my way around the C# compiler with it's limits of "inherited instantiated generic classes".
Anyway, This is my test case:
class Program
{
static void Main(string[] args)
{
var x = new InClass();
Console.WriteLine(x.Test(10)); //prints foo
Console.ReadLine();
}
}
class BaseClass<Foo, Bar>
{
public virtual Foo Test(Bar b)
{
return default(Foo);
}
public virtual string Test(int b)
{
return "foo"; ;
}
}
class InClass : BaseClass<string, int>
{
/*public override string Test(int b)
{
return "bar";
}*/
}
I would think that this declaration of InClass would throw a compiler error, as it makes Test ambiguous. It also makes the non-generic Test impossible to call within the InClass. Notice I have some code commented out in InClass as well. If I uncomment that code, I do get a compiler error.
Is there a mention of this behavior at all in the C# spec, or is this an unheard of edge case?
I would think that this declaration of InClass would throw a compiler error, as it makes Test ambiguous.
Nope. The specification calls out this kind of thing explicitly in section 7.5.3.6:
While signatures as declared must be unique, it is possible that substitution of type arguments might result in identical signatures. In such cases, the tie-breaking rules of overload resolution above will pick the most specific member.
The following examples show overloads that are valid and invalid according to this rule.
(Examples follow, obviously.)
So the language designers have considered it, but presumably the alternatives would be worse. (It would be annoying to not be able to create a class such as InClass even when you didn't want to call Test, for example.)
See this related question:
Why aren't generic type constraints inheritable/hierarchically enforced
Eric Lippert provides a thorough exploration of the potential implications.
The inherited generic types are not enforced by the compiler because they dev team felt it was a rabbit hole not worth going down.
My guess is that method with (int x) signature is better match for int argument than one with generic type Bar that happens to be int in this particular case.
This is issue about LANGUAGE DESIGN.
Please do not answer to the question until you read entire post! Thank you.
With all helpers existing in C# (like lambdas, or automatic properties) it is very odd for me that I cannot pass property by a reference. Let's say I would like to do that:
foo(ref my_class.prop);
I get error so I write instead:
{
var tmp = my_class.prop;
foo(tmp);
my_class.prop = tmp;
}
And now it works. But please notice two things:
it is general template, I didn't put anywhere type, only "var", so it applies for all types and number of properties I have to pass
I have to do it over and over again, with no benefit -- it is mechanical work
The existing problem actually kills such useful functions as Swap. Swap is normally 3 lines long, but since it takes 2 references, calling it takes 5 lines. Of course it is nonsense and I simply write "swap" by hand each time I would like to call it. But this shows C# prevents reusable code, bad.
THE QUESTION
So -- what bad could happen if compiler automatically create temporary variables (as I do by hand), call the function, and assign the values back to properties? Is this any danger in it? I don't see it so I am curious what do you think why the design of this issue looks like it looks now.
Cheers,
EDIT As 280Z28 gave great examples for beating idea of automatically wrapping ref for properties I still think wrapping properties with temporary variables would be useful. Maybe something like this:
Swap(inout my_class.prop1,inout my_class.prop2);
Otherwise no real Swap for C# :-(
There are a lot of assumptions you can make about the meaning and behavior of a ref parameter. For example,
Case 1:
int x;
Interlocked.Increment(ref x);
If you could pass a property by ref to this method, the call would be the same but it would completely defeat the semantics of the method.
Case 2:
void WaitForCompletion(ref bool trigger)
{
while (!trigger)
Thread.Sleep(1000);
}
Summary: A by-ref parameter passes the address of a memory location to the function. An implementation creating a temporary variable in order to "pass a property by reference" would be semantically equivalent to passing by value, which is precisely the behavior that you're disallowing when you make the parameter a ref one.
Your proposal is called "copy in - copy out" reference semantics. Copy-in-copy-out semantics are subtly different from what we might call "ref to variable" semantics; different enough to be confusing and wrong in many situations. Others have already given you some examples; there are plenty more. For example:
void M() { F(ref this.p); }
void F(ref int x) { x = 123; B(); }
void B() { Console.WriteLine(this.p); }
If "this.p" is a property, with your proposal, this prints the old value of the property. If it is a field then it prints the new value.
Now imagine that you refactor a field to be a property. In the real language, that causes errors if you were passing a field by ref; the problem is brought to your attention. With your proposal, there is no error; instead, behaviour changes silently and subtly. That makes for bugs.
Consistency is important in C#, particularly in parts of the language that people find confusing, like reference semantics. I would want either references to always be copy-in-copy-out or never copy-in-copy-out. Doing it one way sometimes and another way other times seems like really bad design for C#, a language which values consistency over brevity.
Because a property is a method. It is a language construct responding to a pattern of encapsulating the setting and retrieval of a private field through a set of methods. It is functionally equivalent to this:
class Foo
{
private int _bar;
public int GetBar( ) { return _bar; }
public void SetBar( ) { _bar = value; }
}
With a ref argument, changes to the underlying variable will be observed by the method, this won't happen in your case. In other words, it is not exactly the same.
var t = obj.prop;
foo(ref t);
obj.prop = t;
Here, side effects of getter and setter are only visible once each, regardless of how many times the "by-ref" parameter got assigned to.
Imagine a dynamically computed property. Its value might change at any time. With this construct, foo is not kept up to date even though the code suggests this ("I'm passing the property to the method")
So -- what bad could happen if
compiler automatically create
temporary variables (as I do by hand),
call the function, and assign the
values back to properties? Is this any
danger in it?
The danger is that the compiler is doing something you don't know. Making the code confusing because properties are methods, not variables.
I'll provide just one simple example where it would cause confusion. Assume it was possible (as is in VB):
class Weird {
public int Prop { get; set; }
}
static void Test(ref int x) {
x = 42;
throw new Exception();
}
static void Main() {
int v = 10;
try {
Test(ref v);
} catch {}
Console.WriteLine(v); // prints 42
var c = new Weird();
c.Prop = 10;
try {
Test(ref c.Prop);
} catch {}
Console.WriteLine(c.Prop); // prints 10!!!
}
Nice. Isn't it?
Because, as Eric Lippert is fond of pointing out, every language feature must be understood, designed, specified, implemented, tested and documented. And it's obviously not a common scenario/pain point.