matthias.lukaszek
matthias.lukaszek

Reputation: 2220

Is it possible to reuse a generic type to call a method with additional constraints?

Example 1

Assume a method that decides which method should be used to convert a value.

public static TTarget ConvertValue<TTarget>(object value)
{
    Type t = typeof(TTarget);
    if (t.IsEnum)
        return ParseEnum<TTarget>(value);
    else //if ...
        return ...;
}

Some methods that handle the value have a generic type parameter with constraints.

public static TEnum ParseEnum<TEnum>(object value)
    where TEnum : struct
{
    //do something
    return ...;
}

The compiler doesn't allow this approach because the type TTarget isn't necessarily a struct and can become NULL and thus cannot be used as TEnum.

Example 2

Assume having a generic method without constraints and a method with additional constraints:

public void DoStuff<T>(T obj)
{
    if (obj is IComparable && obj is ICloneable)
        DoSpecialStuff<T>(obj);
}
public void DoSpecialStuff<T>(T obj)
    where T : IComparable, ICloneable
{
}

This also doesn't work because there is (afaik) no way to cast to multiple interfaces.

Is it possible to reuse a generic type to call a method with additional constraints?

Upvotes: 0

Views: 1126

Answers (3)

supercat
supercat

Reputation: 81217

If for each interface of interest you define a version with an "extra" generic parameter T which inherits both the generic interface and ISelf<out T> (I'd suggest that interface contain a single read-only property "self", of type T), and if each class of interest implements ISelf<ItsOwnType>, then if e.g. IFoo<T> inherits from IFoo and T, a routine that needs something to implement both IFoo and IBar could accept a parameter of type IFoo<IBar>. If the passed-in parameter is called X, X will implement IFoo and X.Self will implement IBar. The beautiful thing about this approach is that any such object implementing any combination of interfaces defined in this way can be typecast to something which will implement any combination of those interfaces, in any order. The one weakness with this approach is that it presumes that objects will define ISelf<T>.Self to return themselves, but there's no real guarantee that such an object won't return something else instead.

Upvotes: 0

LukeH
LukeH

Reputation: 269498

As SLaks mentions, the only way to achieve this is by using reflection. Here's one way of doing it. (The delegates are cached in a dictionary so that subsequent calls for the same type don't need reflection.)

public static TTarget ConvertValue<TTarget>(this object value)
{
    Type t = typeof(TTarget);

    if (t.IsEnum)
    {
        Delegate del = _delegateCache.GetOrAdd(t, t2 =>
            Delegate.CreateDelegate(typeof(Func<object, TTarget>),
                                    _parseEnumInfo.MakeGenericMethod(t2));
        return ((Func<object, TTarget>)del)(value);
    }
    else // if ...
        return ...;
}

private static readonly MethodInfo _parseEnumInfo =
    typeof(YourParentClass).GetMethod("ParseEnum");

private static readonly ConcurrentDictionary<Type, Delegate> _delegateCache =
    new ConcurrentDictionary<Type, Delegate>();

public static TEnum ParseEnum<TEnum>(object value)
    where TEnum : struct, IComparable, IConvertible, IFormattable
{
    // do something
    return ...;
}

Or, to match your second example:

public void DoStuff<T>(T obj)
{
    if ((obj is IComparable) && (obj is ICloneable))
    {
        Delegate del = _delegateCache.GetOrAdd(typeof(T), t =>
            Delegate.CreateDelegate(typeof(Action<T>),
                                    this,
                                    _doSpecialStuffInfo.MakeGenericMethod(t));
        ((Action<T>)del)(obj);
    }
}

private static readonly MethodInfo _doSpecialStuffInfo =
    typeof(YourParentClass).GetMethod("DoSpecialStuff");

private readonly ConcurrentDictionary<Type, Delegate> _delegateCache =
    new ConcurrentDictionary<Type, Delegate>();

public void DoSpecialStuff<T>(T obj)
    where T : IComparable, ICloneable
{
}

Upvotes: 2

SLaks
SLaks

Reputation: 887777

You need to invoke the method using Reflection.
There is no better way.

You should consider calling a non-generic method instead (and passing typeof(TTarget) as a parameter)—ParseEnum shouldn't need to be generic.

Upvotes: 3

Related Questions