Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generic type constraint checking

Tags:

c#

generics

Is it possible to write something that will fail either at compile or runtime (i.e. by any means) on invocation of just one of these methods?

public static void Register<TInterface, TImplementation>()
    where TImplementation : class, TInterface
{
}
public static void RegisterRestrictive<TInterface, TImplementation>()
    where TInterface : class
    where TImplementation : class, TInterface
{
}

The following will pass both for example:

public interface IInterface
{
}

public class Implementation : IInterface
{
}


public void Test()
{
    Register<IInterface, Implementation>();
    RegisterRestrictive<IInterface, Implementation>();
}

I don't think it is, because you can't extend structs?

Asking because of this https://github.com/aspnet/DependencyInjection/pull/624

like image 968
Mardoxx Avatar asked Dec 18 '22 01:12

Mardoxx


2 Answers

The question is, as I understand:

class C<T, U> where T : class, U where U : class { }
class D<T, U> where T : class, U { }

Is there a construction that is legal for D that is not legal for C?

Not if U and T are closed types. That is, types that have no type parameters in them. As Jon Hanna's answer points out, an open type can cause a problem here:

class N<T, U> where T : class, U { C<T, U> c; D<T, U> d; }

D's constraints are not met, so this construction is illegal.

For type arguments which are closed types we can reason as follows:

In C, U is required by constraint to be a reference type.

In D, T can be a class, interface, delegate or array. In every case, U has to be identical to T, or a base class of T, or something T is convertible to via (possibly variant) implicit reference conversion. No matter what, U is a reference type.

Note though that neither the C# compiler, nor the CLR verifier, nor the JIT compiler are required to deduce that U is always going to be a reference type! The C# compiler can and will in this circumstance, for instance, generate unnecessary boxing instructions on usages of U, even though you and I know that U is not going to be a value type under construction.

This can lead to situations where a U is boxed and then immediately unboxed, and the last time I checked -- which was, uh, ten years ago -- the jitter did not generate optimal code for that scenario. Since no doubt the jitter has been rewritten one or more times since I last checked, you should probably not take my word for it.

The good practice here is to put the constraint on there and spell it out.


Some fun related facts:

You can get into similar situations by pulling shenanigans like

class B<T> { public virtual void M<U>(U u) where U : T {} }
class D : B<int> { public override void M<U>(U u) { } }

Note that C# does NOT allow you to re-state the constraint, which is now where U : int. But you cannot do a generic construction of M with anything other than int.

This can lead to some truly bizarre scenarios in IL generation, because the CLR has a poorly-documented rule whereby it does not allow the "known-to-be-a-reference-type"-ness of a type parameter to change across a virtual override. I redid the codegen for such methods to try to get something that would compile, pass the verifier, and jit efficiently several times before giving up and going back to whatever C# 2 did.

like image 196
Eric Lippert Avatar answered Dec 30 '22 05:12

Eric Lippert


Is it possible to write something that will fail either at compile or runtime (i.e. by any means) on invocation of just one of these methods?

Yes, this fails at compile time:

public static void CallThem<TA, TB>()
    where TB : class, TA
{
    Register<TA, TB>();         // Fine
    RegisterRestrictive<TA, TB>(); // CS0452
}

There's no pair of concrete types that match TInterface and TImplementation for only one of them, but the type-parameter types of a calling method certainly can, and type-parameter types are types we need to consider in designing APIs as well a concrete types.

Constraints do not involve inference of other constraints.

like image 30
Jon Hanna Avatar answered Dec 30 '22 06:12

Jon Hanna