我看到一些非常奇怪的东西,我无法解释。我猜测一些我不熟悉的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; }
//...
}
这里发生了什么?
答案 0 :(得分:3)
==
运算符因您的类型失败而导致类型失败。 ==
运算符正常工作,因为object's
使用了==
而不是EquatableIdentifiableObject's
。 GetObject
中,运算符正确评估,因为它不是EquatableIdentifiableObject's
正在使用的==
实现。在C#中,泛型在运行时被解析(至少在这里是相关的意义上),而不是在编译时。请注意,==
是静态的而不是虚拟的。因此,类型T
在运行时被解析,但是必须在编译时解析对==
的调用。在编译器解析==
的编译时,它将不知道使用EquatableIdentifiableObject's
的{{1}}实现。由于类型T具有此约束:==
,因此将使用where T : DBObject, new()
实现(如果有)。如果DBObject's
未定义DBObject
,则将使用执行此操作的第一个基类(最多==
)的实现。关于object
EquatableIdentifiableObject's
的实施的更多评论:
==
with:
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;
}
with:
public static bool operator !=(EquatableIdentifiableObject self, EquatableIdentifiableObject other)
{
...
}
public static bool operator !=(EquatableIdentifiableObject self, EquatableIdentifiableObject other)
{
return !(self == other);
}
定义签名的方式略有误导。第一个参数名为==
,第二个参数名为self
。如果other
是实例方法,那就没问题。由于它是静态方法,因此名称==
有点误导。更好的名称将是self
和o1
或沿此行的某些东西,以便在更平等的基础上处理这两个操作数。 答案 1 :(得分:1)
您现在知道operator ==(...)
可能会有多次重载。其中一些可能是C#内置重载,而其他可以是用户定义的运算符。
如果您将鼠标悬停在Visual Studio中的!=
或==
符号上,它将显示过载分辨率选择的过载(直到VS2013,它只会在选定的过载时显示实际上是一个用户定义的,在VS2015中它将在我认为的所有情况下显示它。
==
的 绑定 (即调用的重载)在编译时静态修复。它没有任何动态或虚拟的东西。所以如果你有:
public T SomeMethod<T>() where T : SomeBaseClass
{
T rv = ...;
if (rv != null)
{
然后在编译时使用通常的重载决策(包括!=
的一些特殊规则)修复要使用的==
的重载。 rv
的类型T
已知为SomeBaseClass
的引用类型eqaul或派生自operator !=(object, object)
。因此,基于此选择最佳过载。如果SomeBaseClass
未定义(或&#34;继承&#34;)适当的重载,则可能是T
重载(内置)。
在运行时,即使SomeEqualityOverloadingClass
的实际替换恰好是更具体的类型virtual
(当然满足约束),这并不意味着新的重载解析将在运行时发生!
这与.Equals(object)
方法dynamic
不同。
在C#中,泛型不像模板那样工作,它们与dynamic
不同。
如果你真的想要if ((dynamic)rv != null)
重载解析(绑定在运行时而不是在编译时),可以说IMAPFolder
。