【问题标题】:Type Inference between generics with reversed constraints具有反向约束的泛型之间的类型推断
【发布时间】:2016-03-22 13:41:22
【问题描述】:

这是 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.

【问题讨论】:

  • 如何在编译时知道要比较的属性 (int Value),但只在运行时知道要比较的类?这似乎是一种脱节。似乎您可以提前构建可比较的类,而不是在运行时插入其信息。另外,必须进行所有类型检查是丑陋的。也许这是无法避免的,但是哎呀。我希望你能让这个更具体。为什么需要比较不同的类?
  • @ErikE [Left|Right]ComparableComparable 是库类。 BarBaz 只是用户代码的示例。

标签: c# generics type-inference


【解决方案1】:

我能否从接受的答案中获得更清晰、无反射的代码,并具有原始的类型推断强度?

你不能。实际上接受的答案并不好,因为它涉及值类型装箱。

同样,你无法避免反思。不过,您可以做的是使用与EqualityComparer&lt;T&gt;.Default implementationComparer&lt;T&gt;.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);
    }
}

【讨论】:

  • 除了“框架就是这样做的”之外,还有什么理由比我的反射版本更喜欢这种方式——这本身就必须有充分的理由?
  • 性能。这是每个 对的一次反射。
  • 啊,所以它是隐式缓存/记忆...反正我正在考虑添加。
  • 基本上,您可以使用通用类静态字段免费获得“缓存”行为。
猜你喜欢
  • 1970-01-01
  • 2016-03-29
  • 2021-06-23
  • 1970-01-01
  • 1970-01-01
  • 2022-11-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多