【问题标题】:Operator '==' cannot be applied to operands of type 'T' and 'T'运算符“==”不能应用于“T”和“T”类型的操作数
【发布时间】:2018-04-04 02:01:09
【问题描述】:

在下面的代码示例中,编译器抱怨x.Id == reference.Id

运算符“==”不能应用于“TId”和“TId”类型的操作数

在 SO 上提出了类似的问题,通过将 ==-operator 替换为 IEquatable<> + EqualsEqualityComparer<TEnum>.Default 来解决这些问题。

但是,由于对这个问题不重要的原因,这两种解决方案都不适用于我。

我不是在寻找 == 运算符的替代品,而是在寻找一个解释为什么相等运算符不适用于泛型类型。

public class Object<TId>
{
    public TId Id { get; set; }

    // Some other object properties...
}

public class ObjectReference<TId>
{
    public TId Id { get; set; }
}

public class ObjectStore<TId>
{
    private List<Object<TId>> _store = new List<Object<TId>>();

    public Object<TId> FindByReference(ObjectReference<TId> reference)
    {
        return _store.FirstOrDefault(x => x.Id == reference.Id);
    }
}

【问题讨论】:

    标签: c# generics


    【解决方案1】:

    由于您在谈论实体框架的问题,我可以假设您的 _store 并不像您的示例代码中的 List 那样(使用其他提到的方法您不会遇到问题)而是某种形式的IQueryable。然后你应该能够通过手动构建Expression 来做你想做的事,就像这样:

    public class ObjectStore<TId> {
        private readonly IQueryable<Object<TId>> _store;
    
        public ObjectStore(IQueryable<Object<TId>> store) {
            _store = store;
        }
    
        public Object<TId> FindByReference(ObjectReference<TId> reference) {
            var refId = reference.Id;
            // x =>
            var arg = Expression.Parameter(typeof(Object<TId>), "x");
            // x.Id == refId
            var equals = Expression.Equal(Expression.Property(arg, "Id"), Expression.Constant(refId));
            // x => x.Id == refId
            var where = (Expression<Func<Object<TId>, bool>>) Expression.Lambda(equals, arg);
            return _store.FirstOrDefault(where);
        }
    }
    

    您也可以将相同的方法应用于您的示例代码,通过使用Compile() 编译表达式并将生成的Func 传递给FirstOrDefault,但我不建议在实践中这样做,除非真的有必要(比如说你真的想打电话给== 运营商,别无其他)。

    【讨论】:

    • 感谢 Evk 的时间和精力。我改变了我的问题,因为它引起了我为什么想知道为什么== 操作员的行为方式如此混乱。虽然您的回答可以解决我的特定问题,但它并没有消除我的困惑。您的回答可能被视为与this one 的重复
    【解决方案2】:

    我不是在寻找 == 运算符的替代品,而是在寻找解释为什么编译器无法确定两个泛型属性属于同一类型。

    没有任何解释可以解释虚假。编译器可以并且确实发现两个泛型属性都属于相同的编译时类型,您可以使用以下内容进行说明:

    x.Id = reference.Id;
    

    编译器将允许该赋值没有问题,因为它知道两个在编译时相同类型之间存在身份转换

    所以你一定在寻找其他事情的解释。我认为您真正要寻找的是为什么运算符重载解析无法找到类型参数上相等的最佳运算符的理由。

    答案是:C# 泛型类型不是 C++ 模板。在 C++ 中,如果您有 ex1 OP ex2,则确定其语义的运算符的解析将执行每个模板构造一次。在 C# 中,我们不这样做;我们对运算符一次执行重载决议,并且必须找到一个适用于类型参数的所有可能替换的运算符。

    对于无约束类型的等式运算符,我们不能这样做;如果TIdobject 则必须执行引用相等;如果是字符串,则必须执行字符串相等,如果是 int,则必须执行 int 相等,如果是“可为空的 Guid”,则必须执行提升为可空的 Guid 相等,依此类推。 C# 中没有广义相等运算符,只有特定相等运算符的集合,由于没有广义运算符,因此没有单个运算符可供运算符重载决策选择。因此你会得到一个错误。

    这就是为什么为了做到这一点,您通常会限制类型以实现一些可以使用的接口;我们可以在泛型类型上泛型调用接口方法。

    您拒绝了该问题的正确解决方案,因此在不了解您拒绝该标准、安全、有效解决方案的原因的情况下,我们无能为力。

    现在,您可能会注意到编译器可以生成代码,该代码在运行时根据运行时类型确定重载解析算法的解析是什么。如果没有过多的性能成本,C# 就无法做到这一点;如果您愿意支付这笔费用,那么将您的操作数转换为dynamic。这告诉编译器您愿意在运行时接受重载解析失败,以换取在编译时没有得到它们;当心!当您关闭安全系统时,您有责任确保程序的类型安全。

    【讨论】:

    • 你说得对,我没有意识到没有一个== 运算符适用于所有类型。这是我一直在寻找的答案,以便完全理解这个限制。还是每天都在学习。谢谢你的时间埃里克!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-06-09
    • 2011-08-14
    • 1970-01-01
    • 2020-05-05
    • 2015-12-11
    • 1970-01-01
    相关资源
    最近更新 更多