Type Inference between generics with reversed constraints

北城余情 提交于 2020-01-16 04:37:12

问题


This is an extension from this question, which had an answer that works in that specific case.

My actual code looks more like this:

public abstract class BaseComparable<TLeft, TRight>
{ }

public class LeftComparable<TLeft, TRight> : BaseComparable<TLeft, TRight> where TLeft : IComparable<TRight>
{
    public LeftComparable(TLeft value) { }
}

public class RightComparable<TLeft, TRight> : BaseComparable<TLeft, TRight> where TRight : IComparable<TLeft>
{
    public RightComparable(TLeft value) { }
}

If you use the equivalent reflection code to what I posted, it works great:

public static BaseComparable<TLeft, TRight> AsComparableFor<TLeft, TRight>(this TLeft left, TRight right)
{
    if (left is IComparable<TRight>)
    {
        var constructor =
            typeof(LeftComparable<,>).MakeGenericType(typeof(TLeft), typeof(TRight))
                                      .GetConstructor(new[] { typeof(TLeft) });
        if (constructor != null)
        {
            return (BaseComparable<TLeft, TRight>)constructor.Invoke(new object[] { left });
        }
    }
    if (right is IComparable<TLeft>)
    {
        var constructor =
            typeof(RightComparable<,>).MakeGenericType(typeof(TLeft), typeof(TRight))
                                      .GetConstructor(new[] { typeof(TLeft) });
        if (constructor != null)
        {
            return (BaseComparable<TLeft, TRight>)constructor.Invoke(new object[] { left });
        }
    }
    throw new ArgumentException();
}

Then you can say

class Baz
{
    public int Value { get; set; }
}
class Bar : IComparable<Baz>
{
    public int Value { get; set; }
    int IComparable<Baz>.CompareTo(Baz other)
    {
        return Value.CompareTo(other.Value);
    }
}

// ....

var bar = new Bar { Value = 1 };
var baz = new Baz { Value = 1 };
var compBaz = baz.AsComparableFor(bar);
var compBar = bar.AsComparableFor(baz);

Fantastic, type inference works exactly as expected.

The adaptation from the accepted answer above, however,

public static class Comparable
{
    public static BaseComparable<TLeft, TRight>
                  AsComparableFor<TLeft, TRight>(this IComparable<TRight> left, TRight right)
    where TLeft : IComparable<TRight>
    {
        if (left is TLeft)
        {
            if (left is IComparable<TRight>)
            {
                return new LeftComparable<TLeft, TRight>((TLeft)left);
            }
        }

        throw new InvalidCastException();
    }

    public static BaseComparable<TLeft, TRight>
                  AsComparableFor<TLeft, TRight>(this TLeft left, IComparable<TLeft> right)
    where TRight : IComparable<TLeft>
    {
        if (left is TLeft)
        {
            if (right is IComparable<TLeft>)
            {
                return new RightComparable<TLeft, TRight>((TLeft)left);
            }
        }

        throw new InvalidCastException();
    }
}

Requires you to explicitly state the type arguments:

//bar.AsComparableFor(baz);
//baz.AsComparableFor(bar); //Does not compile

bar.AsComparableFor<Bar, Baz>(baz);
baz.AsComparableFor<Baz, Bar>(bar); // Does compile

A big part of this was to make the library as painless as possible, and I feel having to specify types defeats that somewhat.

Is there a middle ground? Can I get the cleaner, reflectionless code from the accepted answer with the type inference strength of the original?

Edit: full code can be found in this gist.


回答1:


Can I get the cleaner, reflectionless code from the accepted answer with the type inference strength of the original?

You can't. Actually the accepted answer is not good because it involves value type boxing.

So again, you cannot avoid reflection. What you can do though is to minimize the reflection by using the same technique as in EqualityComparer<T>.Default implementation, Comparer<T>.Default etc. The only difference would be that instead of creating a singleton instance, we'll create a singleton factory delegate:

public abstract class BaseComparable<TLeft, TRight>
{
    public static readonly Func<TLeft, BaseComparable<TLeft, TRight>> Factory = CreateFactory();
    private static Func<TLeft, BaseComparable<TLeft, TRight>> CreateFactory()
    {
        Type genericTypeDefinition;
        if (typeof(IComparable<TRight>).IsAssignableFrom(typeof(TLeft)))
            genericTypeDefinition = typeof(LeftComparable<,>);
        else if (typeof(IComparable<TLeft>).IsAssignableFrom(typeof(TRight)))
            genericTypeDefinition = typeof(RightComparable<,>);
        else
            throw new ArgumentException();
        var parameter = Expression.Parameter(typeof(TLeft), "value");
        var body = Expression.New(genericTypeDefinition
            .MakeGenericType(typeof(TLeft), typeof(TRight))
            .GetConstructor(new[] { typeof(TLeft) }), parameter);
        var lambda = Expression.Lambda<Func<TLeft, BaseComparable<TLeft, TRight>>>(body, parameter);
        return lambda.Compile();
    }
}


public static class BaseComparable
{
    public static BaseComparable<TLeft, TRight> AsComparableFor<TLeft, TRight>(this TLeft left, TRight right)
    {
        return BaseComparable<TLeft, TRight>.Factory(left);
    }
}


来源:https://stackoverflow.com/questions/36155997/type-inference-between-generics-with-reversed-constraints

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!