Why does this generics scenario cause a TypeLoadException?

我的梦境 提交于 2019-11-29 05:36:39
kvb

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.

The only explanation is that the constraint is considered as being part of the method declaration. That is why in the first case it is a compiler error.

The compiler not getting the error when you use object... well, that is a bug of the compiler.

Other "constraints" have the same properties of the generic contraint:

interface I
{
    object M();
}

class C
{
    public some_type M() { return null; }
}

class D : C, I
{
}

I could ask: why this does not work?

You see? This is quite the same question as yours. It is perfectly valid to implement object with some_type, but neither the run-time, nor the compiler will accept it.

If you try to generate MSIL code, and force the implementation of my example, the run-time will complain.

Implicit interface implementation has a requirement that the generic constraints on the method declarations be equivalent, but not necessarily exactly the same in code. Additionally, generic type parameters have an implicit constraint of "where T : object". That is why specifying C<Object> compiles, it causes the constraint to become equivalent to the implicit constraint in the interface. (Section 13.4.3 of the C# Language Spec).

You're also correct that using an explicit interface implementation that calls into your constrained method will work. It provides a very clear mapping from the interface method to your implementation in the class where the constraints cannot differ, and then proceeds to call a similarly-named generic method (one that now has nothing to do with the interface). At that point, constraints on the secondary method can be resolved in the same way as any generic method call without any interface resolution issues.

Moving the constraints from the class to the interface, in your second example, is better because the class will take its constraints from the interface by default. This also means that you must specify the constraints in your class implementation, if applicable (and in the case of Object it is not applicable). Passing I<string> means that you can't directly specify that constraint in code (because string is sealed) and so it must either be part of an explicit interface implementation or a generic type that will be equal to the constraints in both places.

As far as I know, the runtime and the compiler use separate verification systems for constraints. The compiler allows this case but the runtime verifier doesn't like it. I want to stress that I don't know for sure why it has a problem with this, but I would guess that it doesn't like the potential in that class definition to not fulfill the interface constraints depending on what T ends up being set to. If anyone else has a definitive answer on this, that would be great.

In response to your interface based snippet:

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

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

I believe the problem is that the compiler is recognizing that:

  1. you haven't declared the necessary type constraints on C.Foo().
  2. if you choose string as your type there is no valid T on C.Foo() since a type cannot inherit from string.

To see this work in practice specify an actual class that could be inherited from as T1.

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

class C : I<MyClass>
{
    public void Foo<T>() where T : MyClass { }
}

public class MyClass
{
}

To show that the string type is not being treated special in any way just add the sealed keyword to the MyClass declaration above to see it fail in the same way if you were to specify T1 as string along with string as the type constraint on C.Foo().

public sealed class MyClass
{
}

This is because string is sealed and cannot form the basis of a constraint.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!