我看到了一些非常奇怪的东西,我无法解释。我猜一些我不熟悉的 C# 边缘情况,或者运行时/发射器中的错误?
我有以下方法:
public static bool HistoryMessageExists(DBContext context, string id)
{
return null != context.GetObject<HistoryMessage>(id);
}
在测试我的应用程序时,我发现它行为异常 - 它为我知道我的数据库中不存在的对象返回true
。所以我停止了该方法,并在"立即"中运行了以下内容:
context.GetObject<HistoryMessage>(id)
null
null == context.GetObject<HistoryMessage>(id)
true
null != context.GetObject<HistoryMessage>(id)
true
GetObject
定义如下:
public T GetObject<T>(object pk) where T : DBObject, new()
{
T rv = Connection.Get<T>(pk);
if (rv != null)
{
rv.AttachToContext(this);
rv.IsInserted = true;
}
return rv;
}
有趣的是,当将表达式转换为object
时,比较的计算是正确的:
null == (object)context.GetObject<HistoryMessage>(id)
true
null != (object)context.GetObject<HistoryMessage>(id)
false
没有相等运算符覆盖。
编辑:事实证明,有一个运算符过载,这是不正确的。但是,为什么在内部方法泛型GetObject
中,相等性会正确计算,在这种情况下rv
属于HistoryMessage
类型。
public class HistoryMessage : EquatableIdentifiableObject
{
public static bool HistoryMessageExists(DBContext context, string id)
{
var rv = context.GetObject<HistoryMessage>(id);
bool b = rv != null;
return b;
}
public static void AddHistoryMessage(DBContext context, string id)
{
context.InsertObject(new HistoryMessage { Id = id });
}
}
public abstract partial class EquatableIdentifiableObject : DBObject, IObservableObject
{
public event PropertyChangedEventHandler PropertyChanged;
[PrimaryKey]
public string Id { get; set; }
//...
}
public abstract partial class EquatableIdentifiableObject
{
//...
public static bool operator ==(EquatableIdentifiableObject self, EquatableIdentifiableObject other)
{
if (ReferenceEquals(self, null))
{
return ReferenceEquals(other, null);
}
return self.Equals(other);
}
public static bool operator !=(EquatableIdentifiableObject self, EquatableIdentifiableObject other)
{
if (ReferenceEquals(self, null))
{
return !ReferenceEquals(other, null);
}
return !self.Equals(other);
}
}
public abstract class DBObject
{
[Ignore]
protected DBContext Context { get; set; }
[Ignore]
internal bool IsInserted { get; set; }
//...
}
这是怎么回事?
- 正如您已经澄清的那样,您的类型的
==
运算符失败,因为您的重载不正确。 - 转换为对象时,
==
运算符工作正常,因为它object's
使用的==
的实现,而不是EquatableIdentifiableObject's
。 - 在方法
GetObject
中,运算符正确计算,因为它不是正在使用==
EquatableIdentifiableObject's
实现。在 C# 中,泛型是在运行时(至少在这里相关的意义上)而不是在编译时解析的。请注意,==
是静态的,而不是虚拟的。 因此,类型T
在运行时解析,但对==
的调用必须在编译时解析。在编译时,当编译器解析==
它将不知道使用==
的实现EquatableIdentifiableObject's
。由于类型 T 具有以下约束:where T : DBObject, new()
,将使用DBObject's
实现(如果有的话)。如果DBObject
没有定义==
那么将使用这样做的第一个基类的实现(最多object
个)。
关于==
EquatableIdentifiableObject's
实现的更多评论:
- 您可以替换此部分:
if (ReferenceEquals(self, null))
{
return ReferenceEquals(other, null);
}
跟:
// If both are null, or both are the same instance, return true.
if (object.ReferenceEquals(h1, h2))
{
return true;
}
- 更换会更强大
public static bool operator !=(EquatableIdentifiableObject self, EquatableIdentifiableObject other)
{
...
}
跟:
public static bool operator !=(EquatableIdentifiableObject self, EquatableIdentifiableObject other)
{
return !(self == other);
}
- 您定义
==
签名的方式略有误导。第一个参数命名为self
,第二个参数命名为other
。如果==
是一个实例方法,那就没问题了。由于它是一个静态方法,因此名称self
有点误导。更好的名称是o1
和o2
或类似的东西,以便将两个操作数放在更平等的基础上。
如您现在所知,可能会有几种operator ==(...)
重载。其中一些可以是 C# 内置重载,而另一些可以是用户定义的运算符。
如果您将鼠标悬停在Visual Studio中的!=
或==
符号上,它将显示通过重载分辨率选择的重载(直到VS2013,它只会在所选重载实际上是用户定义的重载时才会显示它,在VS2015中,我相信在所有情况下都会显示它)。
==
的绑定(即调用哪个重载)在编译时静态固定。它不是动态的或虚拟的。因此,如果您有:
public T SomeMethod<T>() where T : SomeBaseClass
{
T rv = ...;
if (rv != null)
{
然后,在编译时,将使用!=
的哪个重载,以通常的重载分辨率(包括一些特殊的==
规则)进行修复。该rv
具有类型T
已知是引用类型 eqaul 或派生自SomeBaseClass
。因此,基于此选择最佳过载。这可能是operator !=(object, object)
重载(内置),如果SomeBaseClass
没有定义(或"继承")适当的重载。
因此,在运行时,即使T
的实际替换恰好是更具体的类型SomeEqualityOverloadingClass
(当然满足约束),这并不意味着新的重载解决方案将在运行时发生!
这与virtual
方法不同.Equals(object)
.
在 C# 中,泛型不像模板那样工作,也不像dynamic
。
如果你真的想要dynamic
重载解析(在运行时而不是在编译时绑定),可以说if ((dynamic)rv != null)
。