driis
driis

Reputation: 164311

Same code acts differently on different machines - what might be the cause ? (CLR version issue ?)

I just finished debugging a problem, where our program crashed on a production server, but never on development machines.

I have made this small program, which I could reproduce the issue with:

using System;
using System.Collections.Generic;
using System.Linq;

namespace RunTimeBug
{
    class Program
    {
        static void Main(string[] args)
        {
            var coll = new Collection {{"Test", new Data()}, {"Test2", new Data()}};

            var dataSequence = coll.Cast<Data>().ToList();
            Console.WriteLine(dataSequence.Count);
        }
    }

    class Collection : Dictionary<string,Data>, IEnumerable<Data>
    {
        public new IEnumerator<Data> GetEnumerator()
        {
            foreach(var v in Values)
                yield return v;
        }
    }

    class Data { }
}

When running on my machine, this code prints "2". When running on the production server, it fails with the following exception:

Unhandled Exception: System.InvalidCastException: Unable to cast object of type 'System.Collections.Generic.KeyValuePair
`2[System.String,RunTimeBug.Data]' to type 'RunTimeBug.Data'.
   at System.Linq.Enumerable.<CastIterator>d__b0`1.MoveNext()
   at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
   at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
   at RunTimeBug.Program.Main(String[] args)

The only difference I can find on these machines, is that the CLR Runtime is version 2.0.50727.4927 on machines, where it works, and version 2.0.50727.1433 on machines where it does not work.

As far as I can tell, the Cast extension method gets the wrong version of IEnumerable on the older machines, but the "right" one on newer machines.

Can anyone explain why I am seeing this ? What has changed between the 2 CLR Runtime versions, that might be causing this ?

Please note, I have already deployed a fix, and I am aware that the Collection class in the code above is poor design because it implements 2 different IEnumerable's. This was found "in the wild" in our product, so I would really like to know the exact cause.

Upvotes: 4

Views: 1645

Answers (3)

Hans Passant
Hans Passant

Reputation: 941715

I think the fix mentioned in the article that Jason linked to has something to do with your code working on later releases. I don't have access to the pre-SP1 version of Enumerable.Cast so it is a bit hard to guess.

One thing's for sure: once the code makes it into Enumerable.CastIterator() then it is guaranteed not to work in your case. That sets up an iterator to convert IEnumerable to IEnumerable<> and that iterator calls GetEnumerator() to initialize the iterator block. That can only call Dictionary.GetEnumerator(), not yours. Casting the KeyValuePair that this IEnumerable returns to Data will of course fail, as the exception tells you.

The current version of Enumerable.Cast() first tries to cast IEnumerable to IEnumerable<>. And that works. CastIterator() isn't used.

Upvotes: 3

Marc Gravell
Marc Gravell

Reputation: 1063058

Why do you think there is a "right" version to pick? Personally I'd be happy for it to throw am ambiguous compiler error at this point. Rather than Cast, perhaps just a regular cast?

var dataSequence = ((IEnumerable<Data>)coll).ToList();

Cast<T> casts the data, not the variable.

BTW, are you recompiling for each? I'm wondering if the issue is the compiler rather than the CLR or BCL.

Upvotes: 2

jason
jason

Reputation: 241661

CLR version 2.0.50727.1433 is .NET 2.0 SP1 whereas CLR version 2.0.50727.4927 includes .NET 3.5 SP1 (for reference, see the Version History of the CLR).

The behavior of Enumerable.Cast changed from .NET 3.5 to .NET 3.5 SP1 as outlined here.

Upvotes: 2

Related Questions