根据 MSDN 中 ==
运算符的文档,
对于预定义的值类型,相等运算符 (==) 如果其操作数的值相等则返回 true,否则返回 false。对于字符串以外的引用类型,== 如果它的两个操作数引用同一个对象,则返回 true。对于字符串类型,== 比较字符串的值。用户定义的值类型可以重载 == 运算符(请参阅运算符)。用户定义的引用类型也可以,尽管默认情况下 == 对于预定义的和用户定义的引用类型的行为如上所述。
那么为什么这个代码片段无法编译呢?
bool Compare<T>(T x, T y) { return x == y; }
我收到错误运算符“==”不能应用于“T”和“T”类型的操作数。我想知道为什么,因为据我了解 ==
运算符是为所有类型预定义的?
编辑:谢谢大家。起初我没有注意到该声明仅涉及引用类型。我还认为为所有值类型提供了逐位比较,我现在知道这是不正确的。
但是,如果我使用引用类型,==
运算符是否会使用预定义的引用比较,或者如果类型定义了,它是否会使用运算符的重载版本?
编辑 2: 通过反复试验,我们了解到 ==
运算符在使用不受限制的泛型类型时将使用预定义的引用比较。实际上,编译器将使用它可以找到的最佳方法来处理受限类型参数,但不会再看下去了。例如,下面的代码将始终打印 true
,即使在调用 Test.test<B>(new B(), new B())
时也是如此:
class A { public static bool operator==(A x, A y) { return true; } }
class B : A { public static bool operator==(B x, B y) { return false; } }
class Test { void test<T>(T a, T b) where T : A { Console.WriteLine(a == b); } }
==
可能很有用。这适用于不重载 operator ==
的 struct
类型(“预定义”类型除外)。作为一个简单的例子,试试这个:var map = typeof(string).GetInterfaceMap(typeof(ICloneable)); Console.WriteLine(map == map); /* compile-time error */
var kvp1 = new KeyValuePair<int, int>(); var kvp2 = kvp1;
,则不能检查 kvp1 == kvp2
,因为 KeyValuePair<,>
是一个结构,它不是 C# 预定义类型,并且它不会重载 operator ==
。然而,var li = new List<int>(); var e1 = li.GetEnumerator(); var e2 = e1;
给出了一个例子,你不能用它来做 e1 == e2
(这里我们有嵌套结构 List<>.Enumerator
(运行时称为 "List`1+Enumerator[T]"
),它不会重载 ==
)。
void
返回 bool
...
正如其他人所说,它仅在 T 被限制为引用类型时才有效。在没有任何约束的情况下,您可以与 null 进行比较,但只能与 null 进行比较 - 对于不可为 null 的值类型,该比较始终为 false。
与其调用 Equals,不如使用 IComparer<T>
- 如果您没有更多信息,EqualityComparer<T>.Default
是一个不错的选择:
public bool Compare<T>(T x, T y)
{
return EqualityComparer<T>.Default.Equals(x, y);
}
除此之外,这避免了拳击/铸造。
“...默认情况下,== 对于预定义和用户定义的引用类型的行为如上所述。”
类型 T 不一定是引用类型,因此编译器无法做出这样的假设。
但是,这将编译,因为它更明确:
bool Compare<T>(T x, T y) where T : class
{
return x == y;
}
跟进其他问题,“但是,如果我使用引用类型, == 运算符是否会使用预定义的引用比较,或者如果类型定义了,它是否会使用运算符的重载版本?”
我原以为泛型上的 == 会使用重载版本,但以下测试证明并非如此。有趣...我很想知道为什么!如果有人知道请分享。
namespace TestProject
{
class Program
{
static void Main(string[] args)
{
Test a = new Test();
Test b = new Test();
Console.WriteLine("Inline:");
bool x = a == b;
Console.WriteLine("Generic:");
Compare<Test>(a, b);
}
static bool Compare<T>(T x, T y) where T : class
{
return x == y;
}
}
class Test
{
public static bool operator ==(Test a, Test b)
{
Console.WriteLine("Overloaded == called");
return a.Equals(b);
}
public static bool operator !=(Test a, Test b)
{
Console.WriteLine("Overloaded != called");
return a.Equals(b);
}
}
}
输出
内联:重载 == 调用
通用的:
按任意键继续 。 . .
跟进 2
我确实想指出将我的比较方法更改为
static bool Compare<T>(T x, T y) where T : Test
{
return x == y;
}
导致调用重载的 == 运算符。我猜如果不指定类型(作为 where),编译器就无法推断它应该使用重载的运算符......尽管我认为即使没有指定类型,它也会有足够的信息来做出决定。
Equals
方法中(而不是在 ==
运算符中)进行实际比较。
T
和 T
之间有 ==
时,会找到最佳重载,给定 T
承载的约束(有一个特殊规则,它永远不会为此设置值类型 (这将给出一个毫无意义的结果),因此必须有一些约束来保证它是一个引用类型)。在您的 Follow Up 2 中,如果您使用 DerivedTest
对象,并且 DerivedTest
派生自 Test
但引入了新的 ==
重载,您将再次遇到“问题” .调用哪个重载,在编译时被“烧录”到 IL 中。
通常,EqualityComparer<T>.Default.Equals
应该使用任何实现 IEquatable<T>
或具有合理 Equals
实现的东西来完成这项工作。
但是,如果由于某种原因 ==
和 Equals
的实现方式不同,那么我在 generic operators 上的工作应该很有用;它支持以下的 operator 版本:
相等(T 值 1,T 值 2)
不相等(T 值 1,T 值 2)
大于(T 值 1,T 值 2)
小于(T 值 1,T 值 2)
大于等于(T 值 1,T 值 2)
小于等于(T 值 1,T 值 2)
这么多答案,没有一个能解释为什么? (乔瓦尼明确要求)......
.NET 泛型不像 C++ 模板。在 C++ 模板中,在知道实际模板参数之后发生重载决议。
在 .NET 泛型(包括 C#)中,重载决议在不知道实际泛型参数的情况下发生。编译器可以用来选择要调用的函数的唯一信息来自泛型参数的类型约束。
==
适用于所有类型,无论是引用类型还是值类型。那应该是我认为你没有回答的问题。
==
不适用于所有值类型。更重要的是,它对所有类型的含义并不相同,因此编译器不知道如何处理它。
==
的情况下创建的自定义结构。您能否在答案中也包含该部分,因为我想这是这里的重点
编译不能知道 T 不能是结构(值类型)。所以你必须告诉它它只能是我认为的参考类型:
bool Compare<T>(T x, T y) where T : class { return x == y; }
这是因为如果 T 可以是值类型,则可能会出现 x == y
格式错误的情况 - 在类型没有定义运算符 == 的情况下。同样的情况也会发生,这更明显:
void CallFoo<T>(T x) { x.foo(); }
这也失败了,因为你可以传递一个没有函数 foo 的类型 T。 C# 强制您确保所有可能的类型始终具有函数 foo。这是由 where 子句完成的。
似乎没有类约束:
bool Compare<T> (T x, T y) where T: class
{
return x == y;
}
应该意识到,虽然 ==
运算符中的 class
受约束的 Equals
继承自 Object.Equals
,而 struct 的约束 ValueType.Equals
覆盖了 ValueType.Equals
。
注意:
bool Compare<T> (T x, T y) where T: struct
{
return x == y;
}
也给出了相同的编译器错误。
到目前为止,我不明白为什么编译器会拒绝值类型相等运算符比较。我确实知道一个事实,这是有效的:
bool Compare<T> (T x, T y)
{
return x.Equals(y);
}
Object.Equals
而是测试引用相等性。例如,Compare("0", 0.ToString())
将返回 false,因为参数将是对不同字符串的引用,这两个字符串的唯一字符都是零。
NullReferenceException
。
好吧,就我而言,我想对相等运算符进行单元测试。我需要在不显式设置泛型类型的情况下调用相等运算符下的代码。 EqualityComparer
的建议没有帮助,因为 EqualityComparer
调用了 Equals
方法,但对等式运算符没有帮助。
以下是我如何通过构建 LINQ
来处理泛型类型。它为 ==
和 !=
运算符调用正确的代码:
/// <summary>
/// Gets the result of "a == b"
/// </summary>
public bool GetEqualityOperatorResult<T>(T a, T b)
{
// declare the parameters
var paramA = Expression.Parameter(typeof(T), nameof(a));
var paramB = Expression.Parameter(typeof(T), nameof(b));
// get equality expression for the parameters
var body = Expression.Equal(paramA, paramB);
// compile it
var invokeEqualityOperator = Expression.Lambda<Func<T, T, bool>>(body, paramA, paramB).Compile();
// call it
return invokeEqualityOperator(a, b);
}
/// <summary>
/// Gets the result of "a =! b"
/// </summary>
public bool GetInequalityOperatorResult<T>(T a, T b)
{
// declare the parameters
var paramA = Expression.Parameter(typeof(T), nameof(a));
var paramB = Expression.Parameter(typeof(T), nameof(b));
// get equality expression for the parameters
var body = Expression.NotEqual(paramA, paramB);
// compile it
var invokeInequalityOperator = Expression.Lambda<Func<T, T, bool>>(body, paramA, paramB).Compile();
// call it
return invokeInequalityOperator(a, b);
}
如果要确保调用自定义类型的运算符,可以通过反射来实现。只需使用您的泛型参数获取类型并检索所需运算符的 MethodInfo(例如 op_Equality、op_Inequality、op_LessThan...)。
var methodInfo = typeof (T).GetMethod("op_Equality",
BindingFlags.Static | BindingFlags.Public);
然后使用 MethodInfo 的 Invoke 方法执行操作符,并将对象作为参数传入。
var result = (bool) methodInfo.Invoke(null, new object[] { object1, object2});
这将调用您的重载运算符,而不是由应用于泛型参数的约束定义的运算符。可能不实用,但在使用包含几个测试的通用基类时可以方便地对运算符进行单元测试。
我写了以下函数查看最新的 msdn。它可以轻松比较两个对象 x
和 y
:
static bool IsLessThan(T x, T y)
{
return ((IComparable)(x)).CompareTo(y) <= 0;
}
return ((IComparable)(x)).CompareTo(y) <= 0;
T
不是 IComparable
怎么办?这不会引发无效的强制转换异常吗?如果 T
是 IComparable
,为什么不将它添加到泛型类型约束中呢?
T
确实实现了该接口。如果没有,那么你会得到一个例外。如果是这样,那么它可能应该是通用约束的一部分。
bool Compare(T x, T y) where T : class { return x == y; }
以上将起作用,因为 == 在用户定义的引用类型的情况下得到处理。在值类型的情况下, == 可以被覆盖。在这种情况下,“!=”也应该被定义。
我认为这可能是原因,它不允许使用“==”进行通用比较。
==
令牌用于两个不同的运算符。如果对于给定的操作数类型存在相等运算符的兼容重载,则将使用该重载。否则,如果两个操作数都是相互兼容的引用类型,则将使用引用比较。请注意,在上面的 Compare
方法中,编译器无法判断第一个含义适用,但可以判断第二个含义适用,因此 ==
标记将使用后一个 ,即使 T
重载等式-check 运算符(例如,如果它是 String
类型)。
.Equals()
对我有用,而 TKey
是通用类型。
public virtual TOutputDto GetOne(TKey id)
{
var entity =
_unitOfWork.BaseRepository
.FindByCondition(x =>
!x.IsDelete &&
x.Id.Equals(id))
.SingleOrDefault();
// ...
}
x.Id.Equals
,而不是 id.Equals
。据推测,编译器对 x
的类型有所了解。
我有 2 个解决方案,它们非常简单。
解决方案 1:将泛型类型变量转换为 object
并使用 ==
运算符。
例子:
void Foo<T>(T t1, T t2)
{
object o1 = t1;
object o2 = t2;
if (o1 == o2)
{
// ...
// ..
// .
}
}
解决方案 2:使用 object.Equals(object, object)
方法。
例子:
void Foo<T>(T t1, T t2)
{
if (object.Equals(t1, t2)
{
// ...
// ..
// .
}
}