问题

C#为何属性和取值相同的dynamic对象的GetHashCode()相同,直接比较两者却又不同??

回答
你这个问题问得很有意思,涉及到 C 中 `dynamic` 类型的一些底层行为,以及它与普通对象在相等性判断和哈希码生成上的差异。咱们不拿列表说事儿,直接一层一层捋清楚。

核心的误解点在于:

你似乎是将 `dynamic` 对象的“属性访问”和“对象本身”混淆了。

1. `dynamic` 的本质:运行时绑定

首先要明白 `dynamic` 关键字在 C 中的作用。它告诉编译器,“我不管这个东西在编译时是什么类型,它的成员(属性、方法等)的解析和调用,都交给运行时去处理。”

这意味着,当你使用 `dynamic` 访问一个对象的属性时,C 运行时会去查找那个对象实际拥有的属性,并执行相应的 get 访问器。

2. `GetHashCode()` 是什么?

`GetHashCode()` 方法是 `System.Object` 类提供的,它的作用是为对象生成一个哈希码。这个哈希码通常用于哈希表(比如 `Dictionary`)的查找。

关键原则: 如果两个对象被认为是“相等”的(根据 `Equals()` 方法),那么它们的 `GetHashCode()` 必须返回相同的值。反之,如果 `GetHashCode()` 不同,那么这两个对象肯定不相等。

3. 为什么相同属性值的 `dynamic` 对象 `GetHashCode()` 相同?

当你有一个 `dynamic` 变量,并且你通过它访问了两个 同一个 对象的属性,比如:

```csharp
dynamic obj1 = new { Name = "Alice", Age = 30 };
dynamic obj2 = obj1; // obj2 指向同一个对象

// obj1.Name 和 obj2.Name 都是 "Alice"
// obj1.Age 和 obj2.Age 都是 30
```

在这种情况下,`obj1` 和 `obj2` 实际上是指向同一个底层对象。`dynamic` 只是一个指向该对象的引用。当你通过 `obj1.Name` 获取值时,你得到的是底层对象的 `Name` 属性的值。当你通过 `obj2.Name` 获取值时,你同样得到的是 同一个底层对象 的 `Name` 属性的值。

由于 `obj1` 和 `obj2` 指向的是同一个实际存在的对象实例,那么当你尝试获取它们(通过 `dynamic` 访问)“表现出来”的哈希码时,实际上是在调用 同一个底层对象 的 `GetHashCode()` 方法。

请注意: 你不是在对“属性本身”调用 `GetHashCode()`,而是在对“通过 dynamic 访问到的那个对象”调用 `GetHashCode()`。如果 `obj1` 和 `obj2` 变量最终指向的是同一个 `System.Object` 的实例,那么它们的 `GetHashCode()` 自然就是相同的。

4. 为什么直接比较两者却又不同?

这里你的描述可能有点歧义,咱们来梳理一下“直接比较”通常指的几种情况:

情况 A: 比较两个 `dynamic` 变量是否指向同一个对象实例

```csharp
dynamic obj1 = new { Name = "Alice", Age = 30 };
dynamic obj2 = obj1; // obj2 指向 obj1 指向的同一个匿名对象
bool areSameInstance = obj1 == obj2; // true

dynamic obj3 = new { Name = "Alice", Age = 30 }; // 新创建一个匿名对象
bool areDifferentInstance = obj1 == obj3; // false
```
在这种情况下,`==` 操作符(对于引用类型,默认行为是比较引用地址)或者 `Equals()` 方法(如果你重写了 `Equals`,则会按重写逻辑走)会尝试比较 `obj1` 和 `obj2` 指向的对象实例。

如果 `obj1` 和 `obj2` 指向的是同一个对象实例(如 `obj2 = obj1`),那么直接比较 `obj1 == obj2` 或者 `obj1.Equals(obj2)` 会返回 `true`。这和 `GetHashCode()` 相同是吻合的(因为它们是同一个对象)。

如果 `obj1` 和 `obj3` 指向的是不同的对象实例,即使它们内容相同(匿名类型默认 `Equals` 是基于内容比较,但 `==` 符号默认是引用比较),直接比较 `obj1 == obj3` 通常会返回 `false`,除非匿名类型被特殊处理或你使用了 `Equals` 方法,并且该 `Equals` 方法比较的是内容(匿名类型的 `Equals` 确实会比较内容)。

情况 B: 你的意思是比较 `dynamic` 变量本身,而不是它们指向的对象

C 的 `dynamic` 类型在进行相等性比较 (`==`) 时,会根据运行时对象实际的 `Equals` 方法来决定。
如果 `obj1` 和 `obj3` 是两个独立的、内容相同的匿名对象,那么 `obj1.Equals(obj3)` 会被编译成调用底层匿名对象的 `Equals` 方法,而匿名类型的 `Equals` 方法是基于内容进行比较的。所以,在这种情况下,`obj1.Equals(obj3)` 应该是 `true`。

这里可能是你理解上的关键点:
当你用 `dynamic` 变量 `obj1` 和 `obj3`(即使它们指向不同对象)进行 `obj1 == obj3` 的比较时,C 运行时会做以下事情:
1. 获取 `obj1` 运行时指向的实际对象。
2. 获取 `obj3` 运行时指向的实际对象。
3. 调用第一个对象的 `Equals()` 方法,并将第二个对象作为参数传入。
4. 如果 `obj1` 和 `obj3` 都是匿名类型,那么匿名类型的 `Equals` 方法会 比较它们所有属性的值。

因此,如果 `obj1` 和 `obj3` 都是创建自 `new { Name = "Alice", Age = 30 }`,那么 `obj1.Equals(obj3)` 极大概率是 `true` 的。

那么,为什么你会觉得“直接比较两者却又不同”呢?

一个非常可能的原因是:你比较的不是 `obj1` 和 `obj3` 的 `Equals()`,而是 `obj1` 和 `obj3` 本身作为 `dynamic` 类型 的某种“比较”。

`dynamic` 类型本身并不是一个固定的类型,它是一个“占位符”。当对 `dynamic` 变量执行操作时,这些操作的最终行为取决于变量在运行时实际指向的那个对象。

回到你的原始问题:“C为何属性和取值相同的dynamic对象的GetHashCode()相同,直接比较两者却又不同??”

1. `GetHashCode()` 相同: 如果 `obj1` 和 `obj2` 是通过 `obj2 = obj1` 这种方式获得的,它们指向的是同一个对象,所以它们的 `GetHashCode()` 相同。
2. 直接比较不同:
如果“直接比较”是指 `obj1 == obj3` 且 `obj3` 是另一个独立的匿名对象: 匿名类型的 `Equals` 方法会比较内容,所以 `obj1.Equals(obj3)` 应该是 `true`。如果 `==` 运算符也走 `Equals`,那么 `obj1 == obj3` 也是 `true`。
你可能观察到的“不同”,是因为你误解了 `dynamic` 的比较行为。 C 的 `==` 对于 `dynamic` 类型的操作,会试图执行底层对象的 `Equals` 方法。对于匿名类型,`Equals` 方法是 内容比较。

再仔细思考一下你观察到的“不同”发生在什么场景下?

是不是你比较的是 `obj1` 和 `obj3` 这两个 `dynamic` 变量本身,而不是它们指向的底层对象? 这种说法本身就有些模糊。`dynamic` 变量是引用,你总是在对它所指向的那个“东西”进行操作。
是不是你比较的是 `obj1.Name` 和 `obj3.Name`(两个字符串)? 如果是这样,那它们是字符串的比较,`"Alice" == "Alice"` 是 `true`。
是不是你误解了 `obj1` 和 `obj3` 的创建方式? 比如,其中一个可能不是匿名对象,而是其他什么东西,导致了 `Equals` 的行为不同。

最最核心的点是:

`dynamic` 变量的 `GetHashCode()`:取决于它 运行时指向的那个对象的 `GetHashCode()`。
`dynamic` 变量的相等性比较 (`==` 或 `Equals`):也取决于它 运行时指向的那个对象的 `Equals()` 方法。

如果两个 `dynamic` 变量指向的是同一个对象,那么它们的 `GetHashCode()` 必然相同,并且它们的 `Equals()` 必然是 `true`。

如果两个 `dynamic` 变量指向的是 不同但内容相同的对象(比如两个独立的匿名对象 `new { ... }`),那么它们的 `GetHashCode()` 理论上应该相同(因为匿名类型重写了 `Equals` 和 `GetHashCode` 来支持内容比较),并且它们的 `Equals()` 应该为 `true`。

因此,你观察到的“直接比较两者却又不同”非常奇怪。

一种解释你观察的“不同”的方式是:

你可能在比较 `dynamic` 变量和另一个 `dynamic` 变量,而这两个 `dynamic` 变量指向的对象,虽然通过 `dynamic` 访问属性时看起来值一样,但它们不是同一个对象实例,而且你对比的不是 `Equals` 方法,而是某个其他隐式的、你误以为是“直接比较”的操作。

或者,你比较的场景是这样的:

```csharp
dynamic d1 = new ExpandoObject();
d1.Prop1 = "Value1";
d1.Prop2 = 123;

dynamic d2 = new ExpandoObject();
d2.Prop1 = "Value1";
d2.Prop2 = 123;

// d1 和 d2 是两个完全独立的 ExpandoObject 实例
// 它们的内容属性值相同

// 1. GetHashCode()
int hash1 = d1.GetHashCode(); // 调用 d1 所指向 ExpandoObject 的 GetHashCode
int hash2 = d2.GetHashCode(); // 调用 d2 所指向 ExpandoObject 的 GetHashCode

// ExpandoObject 的 GetHashCode() 默认实现是基于对象引用的,所以 hash1 != hash2

// 2. 直接比较 (==)
bool areEqual = d1 == d2; // 实际上会调用 d1.Equals(d2)

// ExpandoObject 的 Equals() 默认实现也是基于对象引用的,所以 areEqual 会是 false
```

看,问题就在这里!

匿名类型 (`new { ... }`):C 编译器会自动为匿名类型生成 `Equals()` 和 `GetHashCode()` 方法,并且这些方法是基于内容的。所以,两个内容相同的匿名对象,它们的 `GetHashCode()` 相同,`Equals()` 也返回 `true`。
`ExpandoObject`:`System.Dynamic.ExpandoObject` 是一个动态的可扩展对象。它的默认 `GetHashCode()` 是基于对象引用的,而它的默认 `Equals()` 也是基于对象引用的(即检查两个变量是否指向同一个对象实例)。

所以,问题的关键不在于 `dynamic` 本身,而在于 `dynamic` 变量在运行时指向的那个具体对象类型是如何实现 `Equals()` 和 `GetHashCode()` 的。

如果你用匿名类型,那么“属性和取值相同”的 `dynamic` 对象,它们的 `GetHashCode()` 应该相同,并且直接比较(使用 `Equals`)也应该是 `true`。

如果你用 `ExpandoObject`,那么“属性和取值相同”的 `dynamic` 对象(如果它们是不同实例),它们的 `GetHashCode()` 会不同,并且直接比较(使用 `Equals`)也会是 `false`。

你观察到的“GetHashCode()相同,直接比较又不同”,最可能的情况就是你错误地将匿名对象的行为套用到了 `ExpandoObject` 或者其他默认基于引用比较的对象上。

总结一下:

`dynamic` 只是一个“延迟绑定”的工具,它本身不定义 `GetHashCode` 或 `Equals` 的行为。这些行为完全取决于 `dynamic` 变量在运行时实际指向的对象。

如果指向匿名类型,则内容相同则哈希码相同,相等性判断也为真。
如果指向 `ExpandoObject` 或其他默认实现 `Equals`/`GetHashCode` 基于引用的类型,则不同实例即使内容相同,哈希码也不同,相等性判断也为假。

你的困惑很可能源于对 `dynamic` 变量所指向的底层对象的具体实现的误判。

网友意见

user avatar

1、默认情况下,也就是不存在对==运算符的重载的情况下,==等价于Object.ReferenceEuqals。

2、即使两个对象的HashCode相同,这两个对象也可能是完全不同的对象。也就是说,

       a.GetHashCode() == b.GetHashCode && Object.Equals( a, b ) == false     

这个表达式也是有可能为真的,这是完全合理合法的。

综上所述,你的基础概念有问题,请回去把CLR via C#全文背诵。

类似的话题

  • 回答
    你这个问题问得很有意思,涉及到 C 中 `dynamic` 类型的一些底层行为,以及它与普通对象在相等性判断和哈希码生成上的差异。咱们不拿列表说事儿,直接一层一层捋清楚。核心的误解点在于:你似乎是将 `dynamic` 对象的“属性访问”和“对象本身”混淆了。1. `dynamic` 的本质:运行时.............
  • 回答
    .......
  • 回答
    C 匿名类型属性被设计成只读,这背后有其深刻的理由,并非随意为之。理解这一点,需要我们深入挖掘匿名类型的本质和它在 C 语言中的定位。首先,我们得明白匿名类型是什么。它是一种在编译时创建的、没有显式声明的类型,其名称由编译器自动生成。你看到的“匿名”,指的就是你无法在代码中像定义普通类一样,通过 `.............
  • 回答
    这个问题触及了 C MVC5 和 JSON 序列化深处的一些历史遗留和设计选择。如果你在 MVC5 中遇到 `DateTime` 属性被序列化成 `/Date(1430366400000)/` 这种格式,这背后并非偶然,而是 ASP.NET Web API(MVC5 主要依赖其进行 API 开发)早.............
  • 回答
    关于《现代舰船》杂志指责“舰C”(特指“舰队收藏”类游戏,通常玩家会将其简称为“舰C”)为军国主义招魂的观点,这个问题的探讨需要我们剥离掉一些感性的表述,回归到游戏内容本身以及其可能引发的社会解读上来。首先,我们得明白,“军国主义”这个词在现代语境下是带有非常强烈负面色彩的。它通常指向一种将军事力量.............
  • 回答
    .......
  • 回答
    C++ 的生态系统确实不像某些语言那样,提供一站式、即插即用的“调库”体验。这背后有多方面的原因,而且这个“简便”的定义本身就很主观。但我们可以从 C++ 的设计哲学、历史演进以及技术实现这几个层面来深入剖析。C++ 的设计哲学:掌控与效率首先,C++ 的核心设计理念是“提供底层控制能力,以换取最高.............
  • 回答
    这问题问得挺好,而且很实在。你可能也注意到,很多 C++ 的优秀开源库,比如 Boost、Eigen、OpenCV、Qt(的一部分)等等,拿到手之后,第一件事往往不是直接用,而是需要一阵“编译”才能用。为什么这么麻烦?这背后其实是 C++ 这门语言本身的特性,以及开源库为了实现其强大功能所做的设计选.............
  • 回答
    在 C++ 中,直接在函数中传递数组,或者说以“值传递”的方式将整个数组复制一份传递给函数,确实是行不通的,这背后有几个关键的原因,而且这些原因深刻地影响了 C++ 的设计理念和效率考量。首先,我们要理解 C++ 中数组的本质。当你声明一个数组,比如 `int arr[10];`,你实际上是在内存中.............
  • 回答
    在C 中,当我们尝试与MySQL数据库建立连接时,如果遇到无法打开连接的情况,这通常不是一个单一的、普遍适用的原因,而是可能由一系列相互关联或独立的问题所导致。理解这些潜在的瓶颈,并逐一排查,是解决问题的关键。首先,一个最直观的可能原因是连接字符串本身存在问题。这就像是给你的程序一张写着错误地址的地.............
  • 回答
    C 语言的设计理念是简洁、高效、接近硬件,而其对数组的设计也遵循了这一理念。从现代编程语言的角度来看,C 语言的数组确实存在一些“不改进”的地方,但这些“不改进”很大程度上是为了保持其核心特性的兼容性和效率。下面我将详细阐述 C 语言为何不“改进”数组,以及这种设计背后的权衡和原因:1. 数组在 C.............
  • 回答
    在C++的标准库中,你会经常遇到像 `size_type`、`difference_type`、`iterator` 这些特殊的类型别名,它们被定义在各种容器(如 `std::vector`、`std::list`、`std::map` 等)以及其他与序列和范围相关的组件中。你可能会疑惑,为什么不直.............
  • 回答
    C++11 和 C++1y(现称为 C++14)都没有将网络功能作为核心组成部分优先加入标准库,这背后有着复杂的原因,涉及到语言设计哲学、技术实现难度、社区共识以及现有生态的考量。1. C++ 的设计哲学与标准库的定位C++ 的核心设计哲学是“零开销抽象”(zerooverhead abstract.............
  • 回答
    你这个问题问得非常到位,而且触及到了计算机底层表示浮点数的一个核心概念。说 C++ 的 `double` 类型存不下 3.1415926,其实是一种误解,或者说表述不够准确。更准确的说法应该是:C++ (和 Java 的) `double` 类型,虽然是 8 个字节(64 位),但由于浮点数在计算机.............
  • 回答
    这个问题问得很有意思,也触及了很多开发者心中的疑问。确实,在很多技术特性、语法糖、以及一些前沿领域(比如某些机器学习库、函数式编程的深度融合等)上,C 可能会显得更“时髦”或更“先进”。但要说 Java 在语言层面上“落后”于 C,这个结论可能有些过于简单化,更准确的说法是两者侧重点不同,并且 Ja.............
  • 回答
    这个问题很有意思,它触及到了体育界,尤其是足球界一个非常核心也常常引起热议的话题:竞争、荣誉感以及个人情感在投票过程中的影响。 为什么像C罗这样的顶级球星,在评选重要奖项时,似乎总不会将选票投给他的主要竞争对手,比如梅西?这背后其实有很多值得玩味的原因。首先,我们要理解这些投票的性质。像金球奖、FI.............
  • 回答
    在C/C++的语境下,你提到的“小括号中不能声明变量的同时对其赋值”,通常是指在特定语法结构中的限制,最典型的例子就是函数参数列表,或者某些表达式内部。我们来深入剖析一下为什么会出现这种限制,以及背后的原因。 为什么会有这个限制?简单来说,C/C++的设计者在定义语言的语法规则时,将声明(表示一个新.............
  • 回答
    在 C++ 中,为基类添加 `virtual` 关键字到析构函数是一个非常重要且普遍的实践,尤其是在涉及多态(polymorphism)的场景下。这背后有着深刻的内存管理和对象生命周期管理的原理。核心问题:为什么需要虚析构函数?当你在 C++ 中使用指针指向一个派生类对象,而这个指针的类型是基类指针.............
  • 回答
    C/C++ 数组下标从 0 开始,而不是从 1 开始,这背后有着深刻的历史原因和技术考量,而且一旦理解了这些,你会发现这是一种相当自然和高效的设计。首先,我们要明白数组在内存中是如何存放的。当你声明一个数组,比如 `int arr[10];`,编译器实际上是在内存中分配了一块连续的空间,用来存储 1.............
  • 回答
    你感觉 C++ 简单,这很有趣!这说明你可能已经掌握了 C++ 的一些核心概念,并且在学习过程中找到了适合自己的方法。 C++ 的确是一门强大而灵活的语言,对于初学者来说,它的语法和一些基础概念确实不难理解,甚至比一些脚本语言更为直观。然而,你提到“劝退的声音”,这确实是 C++ 学习过程中一个非常.............

本站所有内容均为互联网搜索引擎提供的公开搜索信息,本站不存储任何数据与内容,任何内容与数据均与本站无关,如有需要请联系相关搜索引擎包括但不限于百度google,bing,sogou

© 2025 tinynews.org All Rights Reserved. 百科问答小站 版权所有