我有一些代码将强类型业务对象映射到匿名类型,然后将其序列化为JSON并通过API公开。
将我的解决方案重组为单独的项目后,我的一些测试开始失败。我已经做了一些挖掘,结果发现Object.Equals
对来自不同程序集的代码返回的匿名类型的行为有所不同 - 我不知道为什么,或者我能做些什么解决它。
在https://github.com/dylanbeattie/AnonymousTypeEquality处有完整的复制码,但实际打破的位数低于此值。此代码位于测试项目中:
[TestFixture]
public class Tests {
[Test]
public void BothInline() {
var a = new { name = "test", value = 123 };
var b = new { name = "test", value = 123 };
Assert.That(Object.Equals(a,b)); // passes
}
[Test]
public void FromLocalMethod() {
var a = new { name = "test", value = 123 };
var b = MakeObject("test", 123);
Assert.That(Object.Equals(a, b)); // passes
}
[Test]
public void FromOtherNamespace() {
var a = new { name = "test", value = 123 };
var b = OtherNamespaceClass.MakeObject("test", 123);
Assert.That(Object.Equals(a, b)); // passes
}
[Test]
public void FromOtherClass() {
var a = new { name = "test", value = 123 };
var b = OtherClass.MakeObject("test", 123);
/* This is the test that fails, and I cannot work out why */
Assert.That(Object.Equals(a, b));
}
private object MakeObject(string name, int value) {
return new { name, value };
}
}
然后在解决方案中有一个单独的类库,只包含这个:
namespace OtherClasses {
public static class OtherClass {
public static object MakeObject(string name, int value) {
return new { name, value };
}
}
}
根据MSDN,&#34;只有当所有属性相同时,相同匿名类型的两个实例才相等。&#34; (我的重点) - 那么为了进行比较,是什么控制两个实例是否为相同匿名类型?我的两个实例具有相同的哈希码,并且两者都显示为<>f__AnonymousType0`2[System.String,System.Int32]
- 但我猜测匿名类型的相等性必须考虑完全限定的类型名称,因此将代码移动到不同的程序集可能会破坏的东西。任何人都有一个明确的来源/链接确切如何实现?
答案 0 :(得分:8)
匿名类型本质上是作用域。您的示例打破了范围,因此类型不同。在当前的C#编译器中,匿名类型不能超越程序集(或模块,更准确)。即使来自两个不同程序集的两个匿名类型具有相同的属性,它们也是两种不同的类型(它们是internal
,因此请注意安全隐患)。第二个是你将匿名类型转发到object
,你知道你做错了。
TL; DR:您正在滥用匿名类型。不要因为它咬你而感到惊讶。
答案 1 :(得分:5)
如果使用Reflector等工具反汇编程序集,您将看到您的匿名类型由每个程序集中的类表示,如下所示(在解组编译器生成的标识符之后) ):
internal sealed class AnonymousType<TName, TValue>
{
private readonly TName _name;
private readonly TValue _value;
public TName name => this._name;
public TValue value => this._value;
public AnonymousType(TName name, TValue value)
{
this._name = name;
this._value = value;
}
public override bool Equals(object value)
{
var that = value as AnonymousType<TName, TValue>;
return that != null &&
EqualityComparer<TName>.Default.Equals(this._name, that._name) &&
EqualityComparer<TValue>.Default.Equals(this._value, that._value);
}
public override int GetHashCode()
{
// ...
}
}
Equals
方法的第一行检查value
是AnonymousType<TName, TValue>
的实例,具体指当前程序集中定义的类。因此,即使它们具有相同的结构,来自不同程序集的匿名类型也永远不会相等。
您可能希望更改测试以比较对象的序列化JSON而不是对象本身。
答案 2 :(得分:2)
匿名类型get被编译为它们所在的程序集中的隐藏类型,如果定义匹配,则会重复使用该类型以提高效率。这意味着不同程序集中的类似AT将具有不同的类型,并且它们的.Equals将执行类型检查。
这是我最喜欢与anon类型有关的事情之一:
void Main()
{
var json = "{ \"name\": \"Dylan\"}";
var x = Deserialize(json, new { name = null as string});
Console.WriteLine(x.name);
}
T Deserialize<T>(string json, T template)
{
return (T) JsonConvert.DeserializeObject(json, typeof(T));
}
将Deserialize方法放在另一个程序集中会很有趣......