RoadieRich
RoadieRich

Reputation: 6566

Using a generic type passed without constraint with a constrained generic type

Lets say we have the following classes:

public interface IFrobnicator<T> { }

class ComparableFrobnicator<T> : IFrobnicator<T> where T : IComparable<T>
{
    public ComparableFrobnicator(T value) { }
}

class EquatableFrobnicator<T> : IFrobnicator<T> where T : IEquatable<T>
{
    public EquatableFrobnicator(T value) { }
}

You can write methods

public IFrobnicator<T> MakeFrobnicatorFromComparable<T>(T value) where T : IComparable<T>
{
    return new ComparableFrobnicator<T>(value);
}

public IFrobnicator<T> MakeFrobnicatorFromEquatable<T>(T value) where T : IEquatable<T>
{
    return new EquatableFrobnicator<T>(value);
}

If I wanted to unify that into a single method, the most obvious way won't compile:

public IFrobnicator<T> MakeFrobnicator<T>(T value)
{
    if (value is IComparable<T>)
    {
        return new ComparableFrobnicator<T>(value);
    }
    else if (value is IEquatable<T>)
    {
        return new EquatableFrobnicator<T>(value);
    }
    else throw new ArgumentException();
}

That raises the following error when compiled:

CS0314 The type 'T' cannot be used as type parameter 'T' in the generic type or method 'UserQuery.ComparableFrobnicator'. There is no boxing conversion or type parameter conversion from 'T' to 'System.IComparable'.

I can't replace new ComparableFrobnicator <T> with new ComparableFrobnicator<IComparable<T>> because that causes problems down the line with getting value back out - you cannot cast from an interface type to a concrete type.

So instead, I went the route of reflection:

public IFrobnicator<T> MakeFrobnicator<T>(T value)
{
    if (value is IComparable<T>)
    {
        var constructor = typeof(ComparableFrobnicator<>).MakeGenericType(typeof(T)).GetConstructor(new[] { typeof(T) });
        return (IFrobnicator<T>)constructor.Invoke(new object[] { value});
    }
    else if (value is IEquatable<T>)
    {
        var constructor = typeof(EquatableFrobnicator<>).MakeGenericType(typeof(T)).GetConstructor(new[] { typeof(T) });
        return (IFrobnicator<T>)constructor.Invoke(new object[] { value });
    }
    else throw new ArgumentException();
}

That seems to work perfectly, but looks like a step backwards in a language with such powerful type inference as I'm used to. Is there something I'm missing, or a better technique I missed?

Upvotes: 2

Views: 115

Answers (2)

valerysntx
valerysntx

Reputation: 526

Try the extention methods approach, The way value.MakeFrobnicator() is implemented helps to resolve generic type params issue

public interface IFrobnicator<T> { }

public class ComparableFrobnicator<T> :IFrobnicator<T> 
                              where T  :IComparable<T> 
 {
    public ComparableFrobnicator(T param) { }
 }

public class EquatableFrobnicator<T> :IFrobnicator<T>
                              where T : IEquatable<T> 
{
   public EquatableFrobnicator(T value) { }
}

 public static class FrobnicatorExtentions
 {
    public static IFrobnicator<T> 
          MakeFrobnicatorFromComparable<T>(this T value)
    where T: IComparable<T>
    {                                                            
      //return new ComparableFrobnicator<T>(value);      
     return value.MakeFrobnicator();
    }

    public static IFrobnicator<T> 
           MakeFrobnicatorFromEquatable<T>(this T value)
    where T : IEquatable<T>
    {
      // return new EquatableFrobnicator<T>(value);
     return value.MakeFrobnicator();
    }  

    public static IFrobnicator<T> 
                  MakeFrobnicator<T>(this IEquatable<T> value) 
    where T: IEquatable<T>
    {
      if (value is T)
      {
        if (value is IEquatable<T>)
        {
          return new EquatableFrobnicator<T>((T)value);
        } 
      }

     throw new InvalidCastException();
    }

   public static IFrobnicator<T> 
                 MakeFrobnicator<T>(this IComparable<T> value) 
   where T : IComparable<T>
  {
     if (value is T)
     {
       if (value is IComparable<T>)
       {
         return new ComparableFrobnicator<T>((T)value);
      }
     }

    throw new InvalidCastException();
  }

}

Upvotes: 2

David
David

Reputation: 1774

Have you tried doing a cast?

public IFrobnicator<T> MakeFrobnicator<T>(T value)
{
    if (value is IComparable<T>)
    {
        return new ComparableFrobnicator<T>((IComparable<T>)value);
    }
    else if (value is IEquatable<T>)
    {
        return new EquatableFrobnicator<T>((IEquatable<T>)value);
    }
    else throw new ArgumentException();
}

I can't remember off the top of my head if that's at all valid. Either way, it's not very open/closed. You could instead write a list of converters that each decide if they are the appropriate type to do the conversion.

Upvotes: 0

Related Questions