简体   繁体   中英

Is there an (elegant) solution to constrain a generic type argument further within a method?

I have a generic base class Foo<T> from which the classes Bar<U> and Bat<T> derive. U derives from T . Bat and Bar are similar implementations that differ only in a few places where values of type U must be handled in a different manner.

In Foo , I have a factory method Create that takes an argument of type T and should create either a Bar or Bat object. It looks roughly like this:

public static IFoo<T> Create(T input) {
  if (input.TypeIdentifier == Types.Bar) {// exemplary type check
    // input is of or derives from `U`
    // return a Bar<U>
  } else 
    return new Bat(input);
}

// usage:
U myU = new ClassThatDerivesFromU();
T myT = new ClassThatDerivesFromT(CouldBe.Of(Type.U));
var myFoo1 = Create(myU); // of type IFoo<U>
var myFoo2 = Create(myT); // of type IFoo<T>

Since T is not a U , I cannot instantiate a Bar object.

One possible solution would be this:

public static U To<T, U>(T input) where U : T {
  return input as U;
}

// to create Bar:
new Bar(To<T, U>(input));

However this is quite the hack imo and would not be usable with structs ( U in this case cannot be a struct anyway due to inheritance, but I have another case where I want to call methods depending on if T is a struct or a class for example).

In C++ a scenario like this can be solved (iirc) by providing several overloads of the Create method with different type constraints and the compiler will check the type T and pick the right method (with either U or T as type constraint).

I'm not aware of a similar built-in solution in C#, but perhaps there is an elegant workaround I can use instead? (Reflection is an obvious answer, but not an option)

Yes, generic variance is allowed when using interfaces. You can declare the generic type parameter within IFoo to be either covariant or contravariant (depending upon the usage). If you wish to use a more derived type, then the type T must be contravariant and IFoo can be declared as follows:

interface IFoo<in T> { ... }

Have a look at this MSDN page for more information on generics and variance in C#.

Update

Once you have the condition that an IFoo<U> is IFoo<T> if U : T (eg the generic type of IFoo is contravariant) then you are free to safely cast within your create method:

return (IFoo<T>)((object)new Bar<U>());

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM