Note that there are some explanatory texts on larger screens.

plurals
  1. POWhy does this generics scenario cause a TypeLoadException?
    text
    copied!<p>This got a bit long-winded, so here's the quick version:</p> <p><strong>Why does this cause a runtime TypeLoadException?</strong> (And should the compiler prevent me from doing it?)</p> <pre><code>interface I { void Foo&lt;T&gt;(); } class C&lt;T1&gt; { public void Foo&lt;T2&gt;() where T2 : T1 { } } class D : C&lt;System.Object&gt;, I { } </code></pre> <p>The exception occurs if you try to instantiate D.</p> <hr> <p>Longer, more exploratory version:</p> <p>Consider:</p> <pre><code>interface I { void Foo&lt;T&gt;(); } class C&lt;T1&gt; { public void Foo&lt;T2&gt;() where T2 : T1 { } } class some_other_class { } class D : C&lt;some_other_class&gt;, I { } // compiler error CS0425 </code></pre> <p>This is illegal because the type constraints on <code>C.Foo()</code> don't match those on <code>I.Foo()</code>. It generates compiler error CS0425.</p> <p>But I thought I might be able to break the rule:</p> <pre><code>class D : C&lt;System.Object&gt;, I { } // yep, it compiles </code></pre> <p>By using <code>Object</code> as the constraint on T2, I'm <em>negating</em> that constraint. I can safely pass any type to <code>D.Foo&lt;T&gt;()</code>, because everything derives from <code>Object</code>.</p> <p>Even so, I still expected to get a compiler error. In a C# <em>language</em> 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. </p> <p>I thought I'd gotten away with it, but the runtime says <em>not so fast</em>. If I try to create an instance of <code>D</code>, I get a TypeLoadException: "Method 'C`1.Foo' on type 'D' tried to implicitly implement an interface method with weaker type parameter constraints." </p> <p>But isn't that error technically wrong? Doesn't using <code>Object</code> for <code>C&lt;T1&gt;</code> negate the constraint on <code>C.Foo()</code>, thereby making it equivalent to - NOT stronger than - <code>I.Foo()</code>? The compiler seems to agree, but the runtime doesn't.</p> <p>To prove my point, I simplified it by taking <code>D</code> out of the equation:</p> <pre><code>interface I&lt;T1&gt; { void Foo&lt;T2&gt;() where T2 : T1; } class some_other_class { } class C : I&lt;some_other_class&gt; // compiler error CS0425 { public void Foo&lt;T&gt;() { } } </code></pre> <p>But:</p> <pre><code>class C : I&lt;Object&gt; // compiles { public void Foo&lt;T&gt;() { } } </code></pre> <p>This compiles and runs perfectly for any type passed to <code>Foo&lt;T&gt;()</code>.</p> <p>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?</p> <p>Interestingly, if the scenario is reversed by moving the constraint from the class to the interface...</p> <pre><code>interface I&lt;T1&gt; { void Foo&lt;T2&gt;() where T2 : T1; } class C { public void Foo&lt;T&gt;() { } } class some_other_class { } class D : C, I&lt;some_other_class&gt; { } // compiler error CS0425, as expected </code></pre> <p>And again I negate the constraint:</p> <pre><code>class D : C, I&lt;System.Object&gt; { } // compiles </code></pre> <p>This time it runs fine!</p> <pre><code>D d := new D(); d.Foo&lt;Int32&gt;(); d.Foo&lt;String&gt;(); d.Foo&lt;Enum&gt;(); d.Foo&lt;IAppDomainSetup&gt;(); d.Foo&lt;InvalidCastException&gt;(); </code></pre> <p>Anything goes, and that makes perfect sense to me. (Same with or without <code>D</code> in the equation)</p> <p>So why does the first way break?</p> <p>Addendum:</p> <p>I forgot to add that there is a simple workaround for the TypeLoadException:</p> <pre><code>interface I { void Foo&lt;T&gt;(); } class C&lt;T1&gt; { public void Foo&lt;T2&gt;() where T2 : T1 { } } class D : C&lt;Object&gt;, I { void I.Foo&lt;T&gt;() { Foo&lt;T&gt;(); } } </code></pre> <p>Explicitly implementing <code>I.Foo()</code> is fine. Only the implicit implementation causes the TypeLoadException. Now I can do this:</p> <pre><code> I d = new D(); d.Foo&lt;any_type_i_like&gt;(); </code></pre> <p>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.</p>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload