Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
232 views
in Technique[技术] by (71.8m points)

c# - Why does this generics scenario cause a TypeLoadException?

This got a bit long-winded, so here's the quick version:

Why does this cause a runtime TypeLoadException? (And should the compiler prevent me from doing it?)

interface I
{
    void Foo<T>();
}

class C<T1>
{
    public void Foo<T2>() where T2 : T1 { }
}

class D : C<System.Object>, I { } 

The exception occurs if you try to instantiate D.


Longer, more exploratory version:

Consider:

interface I
{
    void Foo<T>();
}

class C<T1>
{
    public void Foo<T2>() where T2 : T1 { }
}

class some_other_class { }

class D : C<some_other_class>, I { } // compiler error CS0425

This is illegal because the type constraints on C.Foo() don't match those on I.Foo(). It generates compiler error CS0425.

But I thought I might be able to break the rule:

class D : C<System.Object>, I { } // yep, it compiles

By using Object as the constraint on T2, I'm negating that constraint. I can safely pass any type to D.Foo<T>(), because everything derives from Object.

Even so, I still expected to get a compiler error. In a C# language sense, it violates the rule that "the constraints on C.Foo() must match the constraints on I.Foo()", and I thought the compiler would be a stickler for the rules. But it does compile. It seems the compiler sees what I'm doing, comprehends that it's safe, and turns a blind eye.

I thought I'd gotten away with it, but the runtime says not so fast. If I try to create an instance of D, I get a TypeLoadException: "Method 'C`1.Foo' on type 'D' tried to implicitly implement an interface method with weaker type parameter constraints."

But isn't that error technically wrong? Doesn't using Object for C<T1> negate the constraint on C.Foo(), thereby making it equivalent to - NOT stronger than - I.Foo()? The compiler seems to agree, but the runtime doesn't.

To prove my point, I simplified it by taking D out of the equation:

interface I<T1>
{
    void Foo<T2>() where T2 : T1;
}

class some_other_class { }

class C : I<some_other_class> // compiler error CS0425
{
    public void Foo<T>() { }
}

But:

class C : I<Object> // compiles
{
    public void Foo<T>() { }
}

This compiles and runs perfectly for any type passed to Foo<T>().

Why? Is there a bug in the runtime, or (more likely) is there a reason for this exception that I'm not seeing - in which case shouldn't the compiler have stopped me?

Interestingly, if the scenario is reversed by moving the constraint from the class to the interface...

interface I<T1>
{
    void Foo<T2>() where T2 : T1;
}

class C
{
    public void Foo<T>() { }
}

class some_other_class { }

class D : C, I<some_other_class> { } // compiler error CS0425, as expected

And again I negate the constraint:

class D : C, I<System.Object> { } // compiles

This time it runs fine!

D d := new D();
d.Foo<Int32>();
d.Foo<String>();
d.Foo<Enum>();
d.Foo<IAppDomainSetup>();
d.Foo<InvalidCastException>();

Anything goes, and that makes perfect sense to me. (Same with or without D in the equation)

So why does the first way break?

Addendum:

I forgot to add that there is a simple workaround for the TypeLoadException:

interface I
{
    void Foo<T>();
}

class C<T1>
{
    public void Foo<T2>() where T2 : T1 { }
}

class D : C<Object>, I 
{
    void I.Foo<T>() 
    {
        Foo<T>();
    }
}

Explicitly implementing I.Foo() is fine. Only the implicit implementation causes the TypeLoadException. Now I can do this:

        I d = new D();
        d.Foo<any_type_i_like>();

But it's still a special case. Try using anything else other than System.Object, and this won't compile. I feel a bit dirty doing this because I'm not sure if it intentionally works this way.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

It's a bug - see Implementing Generic Method From Generic Interface Causes TypeLoadException and Unverifiable Code with Generic Interface and Generic Method with Type Parameter Constraint. It's not clear to me whether it's a C# bug or a CLR bug, though.

[Added by OP:]

Here's what Microsoft says in the second thread you linked to (my emphasis):

There is a mismatch between the algorithms used by the runtime and the C# compiler to determine if one set of constraints is as strong as another set. This mismatch results in the C# compiler accepting some constructs that the runtime rejects and the result is the TypeLoadException you see. We are investigating to determine if this code is a manifestation of that problem. Regardless, it is certainly not "By Design" that the compiler accepts code like this that results in a runtime exception.

Regards,

Ed Maurer C# Compiler Development Lead

From the part I bolded, I think he's saying this is a compiler bug. That was back in 2007. I guess it's not serious enough to be a priority for them to fix it.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...