This question already has answers here:
Compile-time and runtime casting c#
(2 answers)
Closed 7 years ago.
I understand that a base class cannot be converted into a derived class. What I don't understand is why this isn't caught at compile time? For example:
class GradeBook
{
}
class ProfessorGradeBook : GradeBook
{
}
class Program
{
static void Main(string[] args)
{
ProfessorGradeBook a = (ProfessorGradeBook)new GradeBook();
//shouldn't this be a compile time error?
}
}
I've looked at other questions on stackoverflow but it still doesn't make sense to me why this would compile? ProfessorGradeBook a = (ProfessorGradeBook)new GradeBook(); would never succeed under any circumstance (right?) so why is this a run-time error instead of a compile-time error?
EDIT:
I already knew why the compiler would never catch this:
GradeBook a = new ProfessorGradeBook();
ProfessorGradeBook b = (ProfessorGradeBook)a;
At run-time, a could be pointing to anything so the compiler should just trust you. I was more concerned with why the compiler would never catch this:
ProfessorGradeBook a = (ProfessorGradeBook)new GradeBook();
I guess the answer that makes the most sense is Eric Lippert's first comment, specifically "the vast majority of developers would never type that line of code" so the compiler team was never concerned with trying to make that an error.
This is a downcast. The compiler can't know if a less typed reference may or may not be castable to a more specialized type.
For example:
public class A {}
public class B {}
A a = new B();
B b = (B)a;
a is typed as A while the object stored there is of type B. BTW, when you try to cast it to B, since an instance of A can be A itself or any derived class of A (including B, but not only B), the compiler can't assume that a won't be castable to B because you're providing the assumption that it will be possible using an explicit cast.
At the end of the day, typing is metadata. If you declare a reference as A you're telling the compiler that whatever you set there will be A, assuming that you're losing compile-time metadata to access more specialized members from a possible derived type. In other words: you're telling the compiler that the reference is A and you don't care about the derived type metadata in compile-time and any downcast will be evaluated during run-time because the compiler can't prove a downcast unless the code is executed and runtime finds that the so-called downcast isn't possible since the explicitly provided type isn't part of the source type hierarchy.
Probably compilers could be able to catch invalid downcasts, but this could also increase build times...
You are right that it would never succeed in practice, but the new instruction is a run-time instruction, not a compile-time instruction, and you are doing an explicit cast to (ProfessorGradeBook) which is basically saying to the compiler: "hey compiler, just trust me it'll work".
And so the compiler does.
There are scenarios where one could use things like Fody or PostSharp to add conversion operators after compilation
The compiler does not catch all statically knowable errors. That runs into the halting problem. The compiler catches a well defined subset of all statically discoverable errors.
Also note, that the compiler is not allowed to be arbitrarily smart. The C# language spec says how smart exactly it must be so that all C# compilers behave the same way.
How smart do you like it? Do you want it to catch this as well?
static void Main(string[] args)
{
var g = new GradeBook();
ProfessorGradeBook a = (ProfessorGradeBook)g;
//shouldn't this be a compile time error?
}
That's harder. We can make it arbitrarily hard.
Related
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 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.
This question already has answers here:
Compile-time and runtime casting c#
(2 answers)
Closed 7 years ago.
You have these classes shown below:
public class A
{
}
public class B : A
{
}
You cast the base class to a type of the derived class
A w = (B) new A();
B x = (B) new A();
This will not work on the run time because you cannot really convert a base class to a derived class.
But why is there no compile time error? why does visual studio allowed me to reach run-time before throwing the error?
There are 2 types of casts
once that clearly not allowed when classes have no common base and hence cast have no chance to succeed. I.e. 'string' to 'int'. Such casts are caught by compiler and cause errors.
casts that have chance to succeed - base to derived have reasonable chance to succeed. Compiler allows such casts.
I believe the reason why (B)new A() is allowed at compile time even if cast is guaranteed to fail is because (B)someObjectOfTypeA can succeed and new A() is definitely one of such "object of type A". Compile time detection likely would require additional infrastructure and was not found beneficial (as this cast immediately fails at runtime hence have low chance to be missed by even most basic testing of your code).
In c# casting is in runtime for user defined classes, that is why compiler doesn't throw error. You can see this for more info. The casting from one basic type (string) to another basic type (int) is known as compile time, as compiler knows string can't be type casted to int! But in user defined classes, there is a chance! :)
Inspired by this question.
Short version: Why can't the compiler figure out the compile-time type of M(dynamic arg) if there is only one overload of M or all of the overloads of M have the same return type?
Per the spec, §7.6.5:
An invocation-expression is dynamically bound (§7.2.2) if at least one of the following holds:
The primary-expression has compile-time type dynamic.
At least one argument of the optional argument-list has compile-time type dynamic and the primary-expression does not have a delegate type.
It makes sense that for
class Foo {
public int M(string s) { return 0; }
public string M(int s) { return String.Empty; }
}
the compiler can't figure out the compile-time type of
dynamic d = // dynamic
var x = new Foo().M(d);
because it won't know until runtime which overload of M is invoked.
However, why can't the compiler figure out the compile-time type if M has only one overload or all of the overloads of M return the same type?
I'm looking to understand why the spec doesn't allow the compiler to type these expressions statically at compile time.
UPDATE: This question was the subject of my blog on the 22nd of October, 2012. Thanks for the great question!
Why can't the compiler figure out the compile-type type of M(dynamic_expression) if there is only one overload of M or all of the overloads of M have the same return type?
The compiler can figure out the compile-time type; the compile-time type is dynamic, and the compiler figures that out successfully.
I think the question you intended to ask is:
Why is the compile-time type of M(dynamic_expression) always dynamic, even in the rare and unlikely case that you're making a completely unnecessary dynamic call to a method M that will always be chosen regardless of the argument type?
When you phrase the question like that, it kinda answers itself. :-)
Reason one:
The cases you envision are rare; in order for the compiler to be able to make the kind of inference you describe, enough information must be known so that the compiler can do almost a full static type analysis of the expression. But if you are in that scenario then why are you using dynamic in the first place? You would do far better to simply say:
object d = whatever;
Foo foo = new Foo();
int x = (d is string) ? foo.M((string)d) : foo((int)d);
Obviously if there is only one overload of M then it is even easier: cast the object to the desired type. If it fails at runtime because the cast it bad, well, dynamic would have failed too!
There's simply no need for dynamic in the first place in these sorts of scenarios, so why would we do a lot of expensive and difficult type inference work in the compiler to enable a scenario we don't want you using dynamic for in the first place?
Reason two:
Suppose we did say that overload resolution has very special rules if the method group is statically known to contain one method. Great. Now we've just added a new kind of fragility to the language. Now adding a new overload changes the return type of a call to a completely different type -- a type which not only causes dynamic semantics, but also boxes value types. But wait, it gets worse!
// Foo corporation:
class B
{
}
// Bar corporation:
class D : B
{
public int M(int x) { return x; }
}
// Baz corporation:
dynamic dyn = whatever;
D d = new D();
var q = d.M(dyn);
Let's suppose that we implement your feature requiest and infer that q is int, by your logic. Now Foo corporation adds:
class B
{
public string M(string x) { return x; }
}
And suddenly when Baz corporation recompiles their code, suddenly the type of q quietly turns to dynamic, because we don't know at compile time that dyn is not a string. That is a bizarre and unexpected change in the static analysis! Why should a third party adding a new method to a base class cause the type of a local variable to change in an entirely different method in an entirely different class that is written at a different company, a company that does not even use B directly, but only via D?
This is a new form of the Brittle Base Class problem, and we seek to minimize Brittle Base Class problems in C#.
Or, what if instead Foo corp said:
class B
{
protected string M(string x) { return x; }
}
Now, by your logic,
var q = d.M(dyn);
gives q the type int when the code above is outside of a type that inherits from D, but
var q = this.M(dyn);
gives the type of q as dynamic when inside a type that inherits from D! As a developer I would find that quite surprising.
Reason Three:
There is too much cleverness in C# already. Our aim is not to build a logic engine that can work out all possible type restrictions on all possible values given a particular program. We prefer to have general, understandable, comprehensible rules that can be written down easily and implemented without bugs. The spec is already eight hundred pages long and writing a bug-free compiler is incredibly difficult. Let's not make it more difficult. Not to mention the expense of testing all those crazy cases.
Reason four:
Moreover: the language affords you many opportunities to avail yourself of the static type analyzer. If you are using dynamic, you are specifically asking for that analyzer to defer its action until runtime. It should not be a surprise that using the "stop doing static type analysis at compile time" feature causes static type analysis to not work very well at compile time.
An early design of the dynamic feature had support for something like this. The compiler would still do static overload resolution, and introduced a "phantom overload" that represents dynamic overload resolution only if necessary.
Blog post introducing phantom methods
Details on phantom methods
As you can see in the second post, this approach introduces a lot of complexity (the second article talks about how type inference would need to be modified to make the approach work out). I'm not surprised that the C# team decided to go with the simpler idea of always using dynamic overload resolution when dynamic is involved.
However, why can't the compiler figure out the compile-time type if M has only one overload or all of the overloads of M return the same type?
The compiler could potentially do this, but the language team decided not to have it work this way.
The entire purpose of dynamic is to have all expressions using dynamic execute with "their resolution is deferred until the program is run" (C# spec, 4.2.3). The compiler explicitly does not perform static binding (which would be required to get the behavior you want here) for dynamic expressions.
Having a fallback to static binding if there was only a single binding option would force the compiler to check this case - which was not added in. As for why the language team didn't want to do it, I suspect Eric Lippert's response here applies:
I am asked "why doesn't C# implement feature X?" all the time. The answer is always the same: because no one ever designed, specified, implemented, tested, documented and shipped that feature.
I think the case of being able to statically determine the only possible return type of a dynamic method resolution is so narrow that it would be more confusing and inconsistent if the C# compiler did it, rather than having across the board behavior.
Even with your example, what if Foo is part of a different dll, Foo could be a newer version at runtime from a binding redirect with additional M's that have a different return type, and then the compiler would have guessed wrong because the runtime resolution would return a different type.
What if Foo is an IDynamicMetaObjectProvider d might not match any of the static arguments and thus it would fall back on it's dynamic behavior which could possibly return a different type.
I was wondering why some casts in C# are checked at compile-time whereas in other cases the responsibility is dumped on CLR. Like above both are incorrect but handled in a different way.
class Base { }
class Derived : Base { }
class Other { }
static void Main(string[] args)
{
Derived d = (Derived)new Base(); //Runtime InvalidCastException
Derived d = (Derived)new Other(); //Compile-time Cannot convert type...
}
While reading "C# in depth" I've found the information on this topic where autor says:
"If the compiler spots that it’s actually impossible for that cast to work, it’ll trigger a compilation error—and if it’s theoretically allowed but actually incorrect at execution time, the CLR will throw an exception."
Does 'theoretically' mean connected by inheritance hierarchy (some another affinity between objects ?) or it is compiler's internal business?
Upcasts can be checked at compile time - the type system guarantees that the cast succeeds.
Downcasts cannot (in general) be checked at compile time, so they are always checked at runtime.
Unrelated types cannot be cast to each other.
The compiler considers only the static types. The runtime checks the dynamic (runtime) type.
Looking at your examples:
Other x = new Other();
Derived d = (Derived)x;
The static type of x is Other. This is unrelated to Derived so the cast fails at compile time.
Base x = new Base();
Derived d = (Derived)x;
The static type of x is now Base. Something of type Base might have dynamic type Derived, so this is a downcast. In general the compiler can't know from the static type of x if it the runtime type is Base, Derived, of some other subclass of Base. So the decision of whether the cast is allowed is left to the runtime.
If your variable is of Base type, is can be theoretically constructed by Derived constructor, thus being a variable of Derived type actually. At compile time, compiler does not bother itself with trying to figure out whether in each particular case such downcast (representing a variable of Base type as an entity of Derived type) is possible.
Your sample is simple - you create a new class and cast it right away. But what if you get Base from somewhere else, e.g., some method call? Compiler just cannot "guess" what your method is going to return and therefore throw on not throw an error.
When you cast Other, compiler sees that there is no possibility that Other is actually Derived and throws an exception.