在对结构集合使用FirstOrDefault()之后,我看到了一些奇怪的行为。我把它分成了这个复制案例。该程序无法编译
using System;
using System.Linq;
namespace MyProgram {
public class Program {
static void Main() {
var users = new User[] {
new User() { UserGuid = Guid.NewGuid(), Username = "user01" },
new User() { UserGuid = Guid.NewGuid(), Username = "user02" }
};
var user = users.FirstOrDefault(u => u.Username == "user01");
Console.WriteLine(user == default(User) ? "not found" : "found");
}
}
public struct User {
public Guid UserGuid;
public string Username;
}
}
编译错误是相当神秘的:
运算符'=='不能应用于'MyProgram.User'和'MyProgram.User'类型的操作数
将结构更改为类可以正常工作 - 但我不知道为什么我无法将结构'实例'与默认值进行比较?
答案 0 :(得分:20)
对于类,==
运算符使用引用相等性。当然,结构是值类型,因此无法通过引用进行比较。结构没有==
的默认实现,因为成员比较并不总是有效的比较,具体取决于类型。
您可以使用Object.Equals
方法,该方法会比较成员:
Console.WriteLine(user.Equals(default(User)) ? "not found" : "found");
或者您可以实施==
来致电Object.Equals
:
public static bool operator ==(User lhs, User rhs)
{
return lhs.Equals(rhs);
}
但是,结构的Equals
的默认实现使用反射,因此非常慢。最好自己实施Equals
,以及==
和!=
(以及可能GetHashCode
):
public override bool Equals(Object obj)
{
return obj is User && Equals((User)obj);
}
public bool Equals(User other)
{
return UserGuid == other.UserGuid && Username == other.Username;
}
public static bool operator ==(User lhs, User rhs)
{
return lhs.Equals(rhs);
}
public static bool operator !=(User lhs, User rhs)
{
return !lhs.Equals(rhs);
}
答案 1 :(得分:2)
你只需要实现它:
public static bool operator == (User u1, User u2)
{
return u1.Equals(u2); // use ValueType.Equals() which compares field-by-field.
}
答案 2 :(得分:1)
在C#中,==
令牌用于表示两个不同的运算符(并非所有语言都对两个运算符使用相同的标记; VB.NET使用标记=
和Is
)。其中一个运算符是可重载的相等性测试,仅在为两个操作数类型定义了重载,或者为一个操作数类型定义了重载以及另一个操作数可以隐式转换的类型的情况下才可用。另一个运算符表示引用相等性测试,并且在等于测试运算符不可用的情况下可用,并且其中一个操作数是从另一个操作数派生的类类型,一个操作数是类类型而另一个是接口类型,或两个操作数都是接口类型。
第一个相等测试运算符不能与任何不为其提供显式覆盖的类型(类,接口或结构)一起使用。如果在第一个相等测试运算符不可用的情况下使用==
标记,则C#将尝试使用第二个运算符[注意其他语言如VB.NET不会这样做;在VB.NET中,尝试使用=
来比较两个没有定义相等测试重载的事情将是一个错误,即使可以使用Is
运算符进行比较。该第二运算符可用于将任何引用类型与相同类型的另一引用进行比较,但不能与结构一起使用。由于没有为结构定义任何类型的相等运算符,因此不允许进行比较。
如果有人想知道为什么==
不会简单地回到可用于所有类型的Equals(Object)
,原因是==
的两个操作数都受类型强制的影响以防止其行为匹配Equals
的方式。例如,1.0f == 1.0和1.0 == 1.0f,都将float
操作数强制转换为double
,但是给出类似(1.0f).Equals(1.0)
的表达式,则无法计算第一个操作数除了float
之外的任何东西。此外,如果==
被映射到Equals
,则C#必须使用不同的令牌来表示参考相等性测试[语言应该做的事情,但显然没有'我想做。。
答案 3 :(得分:0)
如果要执行此操作,可以重载==
运算符
public static bool operator ==(User u1, User u2)
{
return u1.Equals(u2)
}
您还应该覆盖Equals
和GetHashCode()
此外,如果您覆盖==
,您可能还想覆盖!=
。
public static bool operator !=(User u1, User u2)
{
return !u1.Equals(u2)
}
答案 4 :(得分:0)
比较两种引用类型时,您将检查引用是否指向相同的类型。
但是如果你正在处理值类型,则没有引用比较。
您必须自己实现操作符,并(可能)检查值类型的字段是否匹配。
答案 5 :(得分:0)
使用:
public static bool IsDefault<TValue>(TValue value) =>
EqualityComparer<TValue>.Default.Equals(value, default(TValue));
或在C#7.1+中:
public static bool IsDefault<TValue>(TValue value) =>
EqualityComparer<TValue>.Default.Equals(value, default);
并考虑实施IEquatable<T>
。
说明
EquityComparer<T>.Default
首先尝试使用IEquatable<T>
界面,逐渐降低到object.Equals
。这既解决了编译器问题,又避免了在实施IEquatable<T>
的情况下基于代价昂贵的基于反射的结构成员比较。
警告
避免使用默认的object.Equals
方法实现==运算符,因为在幕后它会使用反射以及根据实例的调用方式对您的实例进行装箱。使用EqualityComparer<T>.Default.Equals
时,请确保您的结构体实现了IEquatable<T>
,否则这种方法也将导致引擎盖下的反射。
详细信息
==运算符是针对对象和.Net类型实现的,因此自定义结构将没有默认的==运算符实现。
由于这种细微差别,在编译通用相等性测试时,例如:
bool IsDefault<TValue> : where TValue : struct => value == default(TValue)
编译器无法确定要生成的IL指令,因为在解析通用类型之前,无法确定正确的相等运算符实现;但是,在C#中,泛型在运行时解析。因此,即使确实为自定义结构实现==运算符,当涉及泛型时,您仍然可能会遇到问题。