在具有相反约束的泛型之间键入推断

时间:2016-03-22 13:41:22

标签: c# generics type-inference

这是来自this question的扩展程序,其答案适用于该特定情况。

我的实际代码看起来更像是这样:

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) { }
}

如果您使用等效的反射代码来发布我的内容,那么效果很好:

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();
}

然后你可以说

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);

太棒了,类型推断与预期完全一致。

然而,从上面接受的答案中进行改编

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();
    }
}

要求您明确说明类型参数:

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

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

这很大一部分是为了让图书馆尽可能轻松,我觉得必须在某种程度上指明类型的失败。

有中间地带吗?我可以使用原始类型推断强度从已接受的答案中获取更清晰,无反射的代码吗?

修改:full code can be found in this gist.

1 个答案:

答案 0 :(得分:1)

  

我是否可以使用原始类型推断强度从已接受的答案中获取更清晰,无反射的代码?

你做不到。实际上接受的答案并不好,因为它涉及价值型拳击。

所以再一次,你无法避免反思。你可以做的是通过使用与EqualityComparer<T>.Default implementationComparer<T>.Default等相同的技术来最小化反射。唯一的区别是,而不是创建一个单例实例,我们将创建一个单例工厂委托:

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);
    }
}