Since I don't know exactly what part of it alone that triggers the error, I'm not entirely sure how to better label it.
This question is a by-product of the SO question c# code seems to get optimized in an invalid way such that an object value becomes null, which I attempted to help Gary with yesterday evening. He was the one that found out that there was a problem, I've just reduced the problem to a simpler project, and want verification before I go further with it, hence this question here.
I'll post a note on Microsoft Connect if others can verify that they too get this problem, and of course I hope that either Jon, Mads or Eric will take a look at it as well :)
It involves:
- 3 projects, 2 of which are class libraries, one of which is a console program (this last one isn't needed to reproduce the problem, but just executing this shows the problem, whereas you need to use reflector and look at the compiled code if you don't add it)
- Incomplete references and type inference
- Generics
The code is available here: code repository.
I'll post a description below of how to make the projects if you rather want to get your hands dirty.
The problem exhibits itself by producing an invalid cast in a method call, before returning a simple generic list, casting it to something strange before returning it. The original code ended up with a cast to a boolean, yes, a boolean. The compiler added a cast from a List<SomeEntityObject>
to a boolean, before returning the result, and the method signature said that it would return a List<SomeEntityObject>
. This in turn leads to odd problems at runtime, everything from the result of the method call being considered "optimized away" (the original question), or a crash with either BadImageFormatException
or InvalidProgramException
or one of the similar exceptions.
During my work to reproduce this, I've seen a cast to void[]
, and the current version of my code now casts to a TypedReference
. In one case, Reflector crashes so most likely the code was beyond hope in that case. Your mileage might vary.
Here's what to do to reproduce it:
Note: There is likely that there are more minimal forms that will reproduce the problem, but moving all the code to just one project made it go away. Removing the generics from the classes also makes the problem go away. The code below reproduces the problem each time for me, so I'm leaving it as is.
I apologize for the escaped html characters in the code below, this is Markdown playing a trick on me, if anyone knows how I can rectify it, please let me know, or just edit the question
- Create a new Visual Studio 2010 solution containing a console application, for .NET 4.0
- Add two new projects, both class libraries, also .NET 4.0 (I'm going to assume they're named ClassLibrary1 and ClassLibrary2)
- Adjust all the projects to use the full .NET 4.0 runtime, not just the client profile
- Add a reference in the console project to ClassLibrary2
- Add a reference in ClassLibrary2 to ClassLibrary 1
- Remove the two Class1.cs files that was added by default to the class libraries
- In ClassLibrary1, add a reference to System.Runtime.Caching
Add a new file to ClassLibrary1, call it DummyCache.cs, and paste in the following code:
using System; using System.Collections.Generic; using System.Runtime.Caching; namespace ClassLibrary1 { public class DummyCache<TModel> where TModel : new() { public void TriggerMethod<T>() { } // Try commenting this out, note that it is never called! public void TriggerMethod<T>(T value, CacheItemPolicy policy) { } public CacheItemPolicy GetDefaultCacheItemPolicy() { return null; } public CacheItemPolicy GetDefaultCacheItemPolicy(IEnumerable<string> dependentKeys, bool createInsertDependency = false) { return null; } } }
Add a new file to ClassLibrary2, call it Dummy.cs and paste in the following code:
using System; using System.Collections.Generic; using ClassLibrary1; namespace ClassLibrary2 { public class Dummy { private DummyCache<Dummy> Cache { get; set; } public void TryCommentingMeOut() { Cache.TriggerMethod<Dummy>(); } public List<Dummy> GetDummies() { var policy = Cache.GetDefaultCacheItemPolicy(); return new List<Dummy>(); } } }
Paste in the following code in Program.cs in the console project:
using System; using System.Collections.Generic; using ClassLibrary2; namespace ConsoleApplication23 { class Program { static void Main(string[] args) { Dummy dummy = new Dummy(); // This will crash with InvalidProgramException // or BadImageFormatException, or a similar exception List<Dummy> dummies = dummy.GetDummies(); } } }
Build, and ensure there are no compiler errors
- Now try running the program. This should crash with one of the more horrible exceptions. I've seen both InvalidProgramException and BadImageFormatException, depending on what the cast ended up as
Look at the generated code of Dummy.GetDummies in Reflector. The source code looks like this:
public List<Dummy> GetDummies() { var policy = Cache.GetDefaultCacheItemPolicy(); return new List<Dummy>(); }
however reflector says (for me, it might differ in which cast it chose for you, and in one case Reflector even crashed):
public List<Dummy> GetDummies() { List<Dummy> policy = (List<Dummy>)this.Cache.GetDefaultCacheItemPolicy(); TypedReference CS$1$0000 = (TypedReference) new List<Dummy>(); return (List<Dummy>) CS$1$0000; }
Now, here's a couple of odd things, the above crash/invalid code aside:
Library2, which has
Dummy.GetDummies
, performs a call to get the default cache policy on the class from Library1. It uses type inferencevar policy = ...
, and the result is anCacheItemPolicy
object (null in the code, but type is important).However, ClassLibrary2 does not have a reference to System.Runtime.Caching, so it should not compile.
And indeed, if you comment out the method in Dummy that is named
TryCommentingMeOut
, you get:The type 'System.Runtime.Caching.CacheItemPolicy' is defined in an assembly that is not referenced. You must add a reference to assembly 'System.Runtime.Caching, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'.
Why having this method present makes the compiler happy I don't know, and I don't even know if this is linked to the current problem or not. Perhaps it is a second bug.
There is a similar method in
DummyCache
, if you restore the method inDummy
, so that the code again compiles, and then comment out the method inDummyCache
that has the "Try commenting this out" comment above it, you get the same compiler error