关于"相等"的疑问:引用类型/值类型/特殊的String/==/ReferenceEquals/

微创社(MCC) 2009-09-08 12:23:39
加精
“Equals”和“==”有什么不同吗?
两个String怎样看是否相等?
string,值类型和引用类型。

关于"相等性"我们了解了吗?

==是什么?!

为什么值类型不能使用==,只能使用 重载方法的 operator ==
为什么值类型可以使用ReferenceEquals

using System;
using System.Text;

class Test
{

static bool ReferenceCompare<U, V>(U obj1, V obj2)
{
return object.ReferenceEquals(obj1, obj2);
}

static bool ClassEqualsCompare<U, V>(U obj1, V obj2)
where U:class
where V:class
{
return obj1 == obj2;
}

//运算符“==”无法应用于“U”和“V”类型的操作数
static bool StructEqualsCompare<U, V>(U obj1, V obj2)
where U : struct
where V : struct
{
return object.ReferenceEquals(obj1, obj2);
//return obj1 == obj2;
}

//项目属性对话框->配置属性->生成->允许不安全代码块->设为true
unsafe static void Main(string[] args)
{
//-----------------------------------------
string s1 = "abc";
string s2 = "abc";
string s3 = s1;

Console.WriteLine(ReferenceCompare(s1, s2));
Console.WriteLine(ReferenceCompare(s1, s3));

Console.WriteLine(ClassEqualsCompare(s1, s2));
Console.WriteLine(ClassEqualsCompare(s1, s3));

Console.WriteLine("s1'code:{0:X8}", s1.GetHashCode());
Console.WriteLine("s2'code:{0:X8}", s2.GetHashCode());
Console.WriteLine("s3'code:{0:X8}", s3.GetHashCode());

//-----------------------------------------
StringBuilder sb1 = new StringBuilder("abc");
StringBuilder sb2 = new StringBuilder("abcD");
StringBuilder sb3 = sb1;

Console.WriteLine(ReferenceCompare(sb1, sb2));
Console.WriteLine(ReferenceCompare(sb1, sb3));

Console.WriteLine(ClassEqualsCompare(sb1, sb2));
Console.WriteLine(ClassEqualsCompare(sb1, sb3));

Console.WriteLine("sb1'code:{0:X8}", sb1.GetHashCode());
Console.WriteLine("sb2'code:{0:X8}", sb2.GetHashCode());
Console.WriteLine("sb3'code:{0:X8}", sb3.GetHashCode());

//-----------------------------------------
int i1 = 100;
int i2 = 100;
int i3 = i1;

Console.WriteLine(ReferenceCompare(i1, i2));
Console.WriteLine(ReferenceCompare(i1, i3));

//ValueType是不可继承的,并且没有==
//值类型派生自ValueType

Console.WriteLine("i1'address:{0:X8}", (uint)&i1);
Console.WriteLine("i2'address:{0:X8}", (uint)&i2);
Console.WriteLine("i3'address:{0:X8}", (uint)&i3);

Console.ReadKey();
}
}


还有更多的相关问题,等待大家来发掘.
...全文
1678 95 打赏 收藏 转发到动态 举报
写回复
用AI写文章
95 条回复
切换为时间正序
请发表友善的回复…
发表回复
xiaoxm_001 2009-12-10
  • 打赏
  • 举报
回复
楼主可以整理下 ,写个博客吖
ginpq 2009-09-11
  • 打赏
  • 举报
回复
先收藏吧,以后慢慢看。
微创社(MCC) 2009-09-11
  • 打赏
  • 举报
回复
@狂奔中的蜗牛

问的好,
原因在于编译器不知道c和d的内容都是字符串引用,
这个解释有一定的倒理,但还没有落在根本上,
问题的关键还在于==运算符的重载判定原则,
具体见60楼和61楼

作一点总结如下:
[1]重载规则(重载规则虽然不是判定规则,但能够提供一些约束条件)
==重载,左/右参数,至少有一个是定义该重载的类型(一个或两个)
==重载,左/右参数不等的情况下,A,B与B,A是不同的==重载
==重载,在一个对象中的定义无非是三种情况:A==A,A==B,B==A
[2]重载规则总突
==重载冲突的判定地点,在调用对象的可见范围内
交叉对象中的==重载可能出现同时定义相同的A==B类型重载,在编译时提示冲突
[3]判定规则(推测,借鉴C++语言)
[A]选择候选函数;
[B]选择可行函数,包括识别每个实参的潜在转换序列;
[C]选择最佳匹配的函数。
[D]如果左操作数具有类类型,而且该类定义了该操作符的重载版本,则候选集将包含操作符的重载版本。
[E]候选函数集由所有与被使用的函数同名的函数构成,被使用的函数可以从函数调用处看到。
[F]对于操作符用在表达式中的情况,候选函数包括操作符的内置版本以及该操作符的普通非成员版本。
class A
{
//A vs A
public static bool operator ==(A lhs, A rhs)
{
System.Console.WriteLine("A == A");
return true;
}
public static bool operator ==(A lhs, A rhs)
{
System.Console.WriteLine("A != A");
return false;
}

//A vs B
public static bool operator ==(A lhs, B rhs)
{
System.Console.WriteLine("A == B");
return true;
}
public static bool operator ==(A lhs, B rhs)
{
System.Console.WriteLine("A != B");
return false;
}

//B vs A
public static bool operator ==(B lhs, A rhs)
{
System.Console.WriteLine("B == A");
return true;
}
public static bool operator ==(B lhs, A rhs)
{
System.Console.WriteLine("B != A");
return false;
}
}

class B
{
//TODO:
}


在本案例中就非常明确了.
==重载有以下两种候远种情况:
string == string (string 本身对==做了重载)
objcet == objcet
c== d 最合适的就是 objcet == objcet
object的==是比较引用,实际上的"地址(不考虑GC)"的比较

顺便说明一下,扩展方法是不能用在operator上的,只能从类/结构内部来定义...
wa_yi 2009-09-11
  • 打赏
  • 举报
回复
我也正在自学C#
scy251147 2009-09-11
  • 打赏
  • 举报
回复
[Quote=引用 51 楼 pcnetman888 的回复:]
补一段Jon  Skeet的总结,比较简洁:

什么时候应该使用==?什么时候应该使用Equals?(原作:Jon  Skeet)

Equals方法只是在System.Object中定义的一个虚拟方法,它由任何选择执行该任务的类所重写。
==运算符是一个可由类重载的运算符,该类通常具有恒等行为。

对于未重载==的引用类型,该运算符会比较两个引用类型是否引用同一对象,
而这恰好是System.Object中的Equals实现所做的工作。

对于未重载==的值类型,该运算符会比较这两个值是否"按位"相等,即是否这两个值中的每个字段都相等。
当您对值类型调用Equals时,仍然会发生这一情况,
但这一次,该实现是由ValueType提供的,并且使用反射进行比较,
从而使比较速度比特定于类型的实现慢很多。

到此为止,二者是如此类似。
二者之间的主要区别是多态。
运算符被重载而不是被重写,这意味着除非编译器知道调用更为具体的版本,否则它只是调用恒等版本。
为阐明这一点,请看下面这个示例:

C# codeusing System;publicclass Test
{staticvoid Main()
{// Create two equal but distinct stringsstring a=newstring(newchar[] {'h','e','l','l','o' });string b=newstring(newchar[] {'h','e','l','l','o' });
Console.WriteLine(a== b);
Console.WriteLine(a.Equals(b));// Now let's see what happens with the same tests but// with variables of type objectobject c= a;object d= b;
Console.WriteLine(c== d);
Console.WriteLine(c.Equals(d));
}
}
结果是:
True
True
False
True

第三行是False,原因在于编译器不知道c和d的内容都是字符串引用,
因而只能调用==的非重载版本。
因为它们是对不同字符串的引用,所以恒等运算符返回False。
[/Quote]

第三个没看懂,能解释下么
ck_se7en 2009-09-11
  • 打赏
  • 举报
回复
很深刻。
josaa 2009-09-11
  • 打赏
  • 举报
回复
微创社(MCC) 2009-09-11
  • 打赏
  • 举报
回复
还是看原版的好,说的很清楚:

Compiler primitive types Your compiler will provide implementations of the == and !=
operators for types that it considers primitives. For example, the C# compiler knows how to
compare Object, Boolean, Char, Int16, Uint16, Int32, Uint32, Int64, Uint64, Single,
Double, Decimal, and so on for equality. In addition, these types provide implementations of
Equals, so you can call this method as well as use operators.

应该翻译成:
编译器为内置类型实现了==和!=运算

Decimal应该排除在外吧?!
(这个是重载过的,难道也这样?!)
wuyi8808 2009-09-11
  • 打赏
  • 举报
回复
[Quote=引用 87 楼 pcnetman888 的回复:]
网上查不到:6.1.3 为值类型实现 Equals 方法
不知道是怎么说的?
[/Quote]


见:http://topic.csdn.net/u/20090911/17/be24c02e-0eed-41fe-bc17-e7f643c473ce.html
微创社(MCC) 2009-09-11
  • 打赏
  • 举报
回复
@空军

好象还不是这个意思,因为Equals调用了==
网上查不到:6.1.3 为值类型实现 Equals 方法
不知道是怎么说的?

给一个Int32的源码
/*
* Int32.cs - Implementation of the "System.Int32" class.
*
* Copyright (C) 2001 Southern Storm Software, Pty Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/

namespace System
{

using System.Private;
using System.Private.NumberFormat;
using System.Globalization;

#if !ECMA_COMPAT && CONFIG_FRAMEWORK_2_0
using System.Runtime.InteropServices;

[ComVisible(true)]
[Serializable]
#endif
public struct Int32 : IComparable, IFormattable
#if !ECMA_COMPAT
, IConvertible
#endif
#if CONFIG_FRAMEWORK_2_0
, IComparable<int>, IEquatable<int>
#endif
{
private int value_;

public const int MaxValue = 0x7FFFFFFF;
public const int MinValue = unchecked((int)(-0x80000000));

// Override inherited methods.
public override int GetHashCode()
{ return (value_); }
public override bool Equals(Object value)
{
if(value is Int32)
{
return (value_ == ((Int32)value).value_);
}
else
{
return false;
}
}

// String conversion.
public override String ToString()
{
return ToString(null, null);
}
public String ToString(String format)
{
return ToString(format, null);
}
public String ToString(IFormatProvider provider)
{
return ToString(null, provider);
}
public String ToString(String format, IFormatProvider provider)
{
return Formatter.FormatInt32( value_, format, provider );
}

// Parsing methods.
public static int Parse(String s, NumberStyles style,
IFormatProvider provider)
{
NumberParser.ValidateIntegerStyle(style);
return NumberParser.ParseInt32
(s, style, NumberFormatInfo.GetInstance(provider), 0);
}
public static int Parse(String s)
{
return Parse(s, NumberStyles.Integer, null);
}
public static int Parse(String s, IFormatProvider provider)
{
return Parse(s, NumberStyles.Integer, provider);
}
public static int Parse(String s, NumberStyles style)
{
return Parse(s, style, null);
}

// Implementation of the IComparable interface.
public int CompareTo(Object value)
{
if(value != null)
{
if(!(value is Int32))
{
throw new ArgumentException(_("Arg_MustBeInt32"));
}
int temp = ((Int32)value).value_;
if(value_ < temp)
{
return -1;
}
else if(value_ > temp)
{
return 1;
}
else
{
return 0;
}
}
else
{
return 1;
}
}

#if CONFIG_FRAMEWORK_2_0

// Implementation of the IComparable<int> interface.
public int CompareTo(int value)
{
return (value_ - value.value_);
}

// Implementation of the IEquatable<int> interface.
public bool Equals(int obj)
{
return (value_ == obj.value_);
}

#endif // CONFIG_FRAMEWORK_2_0

#if !ECMA_COMPAT

// Implementation of the IConvertible interface.
public TypeCode GetTypeCode()
{
return TypeCode.Int32;
}
bool IConvertible.ToBoolean(IFormatProvider provider)
{
return Convert.ToBoolean(value_);
}
byte IConvertible.ToByte(IFormatProvider provider)
{
return Convert.ToByte(value_);
}
sbyte IConvertible.ToSByte(IFormatProvider provider)
{
return Convert.ToSByte(value_);
}
short IConvertible.ToInt16(IFormatProvider provider)
{
return Convert.ToInt16(value_);
}
ushort IConvertible.ToUInt16(IFormatProvider provider)
{
return Convert.ToUInt16(value_);
}
char IConvertible.ToChar(IFormatProvider provider)
{
return Convert.ToChar(value_);
}
int IConvertible.ToInt32(IFormatProvider provider)
{
return value_;
}
uint IConvertible.ToUInt32(IFormatProvider provider)
{
return Convert.ToUInt32(value_);
}
long IConvertible.ToInt64(IFormatProvider provider)
{
return Convert.ToInt64(value_);
}
ulong IConvertible.ToUInt64(IFormatProvider provider)
{
return Convert.ToUInt64(value_);
}
float IConvertible.ToSingle(IFormatProvider provider)
{
return Convert.ToSingle(value_);
}
double IConvertible.ToDouble(IFormatProvider provider)
{
return Convert.ToDouble(value_);
}
Decimal IConvertible.ToDecimal(IFormatProvider provider)
{
return Convert.ToDecimal(value_);
}
DateTime IConvertible.ToDateTime(IFormatProvider provider)
{
throw new InvalidCastException
(String.Format
(_("InvalidCast_FromTo"), "Int32", "DateTime"));
}
Object IConvertible.ToType(Type conversionType, IFormatProvider provider)
{
return Convert.DefaultToType(this, conversionType,
provider, true);
}

#endif // !ECMA_COMPAT

}; // class Int32

}; // namespace System
wuyi8808 2009-09-11
  • 打赏
  • 举报
回复
[Quote=引用 85 楼 pcnetman888 的回复:]
@空军

下面这个问题有定论嘛?

如何获得一个class类型类量的指针
http://topic.csdn.net/u/20090905/09/85f03475-1801-42a1-b7ba-fd7c5c4987ae.html
[/Quote]

sorry,这个问题没有研究过。
微创社(MCC) 2009-09-11
  • 打赏
  • 举报
回复
@空军

下面这个问题有定论嘛?

如何获得一个class类型类量的指针
http://topic.csdn.net/u/20090905/09/85f03475-1801-42a1-b7ba-fd7c5c4987ae.html
微创社(MCC) 2009-09-11
  • 打赏
  • 举报
回复
终于可以结贴了。

我一直是猜测是这样,只是没有办法通过代码去验证,呵呵。

编译器认为的基元类型:和使用操作符一样来调用它们的Equals方法
微创社(MCC) 2009-09-11
  • 打赏
  • 举报
回复
好的,空军,接受你的建议,去弄一本看看。

呵呵,先自己研究,后看书也有好处。

不受书中内容的干扰,
这样虽然走一些弯路,
收获更大。
(特别是自己写代码去验证,如果你知道了答案,一定不会这样去做。)
wuyi8808 2009-09-11
  • 打赏
  • 举报
回复
Jeffrey Richter 的《Microsoft .NET 框架程序设计(修订版)》 是基于 .net framework 1.1 的,
同一作者的《框架设计(第2版) CLR Via C#》是基于 .net framework 2.0 的,建议看后者。

wuyi8808 2009-09-11
  • 打赏
  • 举报
回复
[Quote=引用 79 楼 pcnetman888 的回复:]

4.1 值类型判等

    * Equals,System.ValueType重载了System.Object的Equals方法,用于实现对实例数据的判等。
    * ReferenceEquals,对值类型应用ReferenceEquals将永远返回false。
    * ==,未重载的==的值类型,将比较两个值是否“按位”相等。

红色标注部分,不知道是个啥意思!!!

[/Quote]

建议看看 Jeffrey Richter 的《Microsoft .NET 框架程序设计(修订版)》(李建忠译)第157-161页:

6.1.3 为值类型实现 Equals 方法
6.1.4 Equals 方法与 == / !== 操作符的实现总结

zhoulin_062 2009-09-11
  • 打赏
  • 举报
回复
对于基本类型。==和equeals是相同的啊,然而对于对象来说,==是比较两个对象是否是同一个对象,equeals是比较两个对象的内容是否相同,此文参考Thinking in java。
微创社(MCC) 2009-09-11
  • 打赏
  • 举报
回复
http://www.kuqin.com/dotnet/20090405/44350.html

4.1 值类型判等

* Equals,System.ValueType重载了System.Object的Equals方法,用于实现对实例数据的判等。
* ReferenceEquals,对值类型应用ReferenceEquals将永远返回false。
* ==,未重载的==的值类型,将比较两个值是否“按位”相等。

红色标注部分,不知道是个啥意思!!!
mxc1225 2009-09-10
  • 打赏
  • 举报
回复
楼主研究的可真细啊!?
松林迷途 2009-09-10
  • 打赏
  • 举报
回复
不错,赞成一下!
加载更多回复(71)
net的最近面试经典试题ASP.NET面试题集合 1. 简述 private、 protected、 public、 internal 修饰符的访问权限。 答 . private : 私有成员, 在类的内部才可以访问。 protected : 保护成员,该类内部和继承类中可以访问。 public : 公共成员,完全公开,没有访问限制。 internal: 在同一命名空间内可以访问。 2 .列举ASP.NET 页面之间传递的几种方式。 答. 1.使用QueryString, 如....?id=1; response. Redirect().... 2.使用Session变量 3.使用Server.Transfer 3. 一列数的规则如下: 1、1、2、3、5、8、13、21、34...... 求第30位数是多少, 用递归算法实现。 答:public class MainClass { public static void Main() { Console.WriteLine(Foo(30)); } public static int Foo(int i) { if (i 0 && i <= 2) return 1; else return Foo(i -1) + Foo(i - 2); } } 4.C#中的委托是什么?事件是不是一种委托? 答 : 委托可以把一个方法作为参数代入另一个方法。 委托可以理解为指向一个函数的引用。 是,是一种特殊的委托 5.override与重载的区别 答 : override 与重载的区别。重载是方法的名称相同。参数或参数类型不同,进行多次重载以适应不同的需要 Override 是进行基类中函数的重写。为了适应需要。 6.如果在一个B/S结构的系统中需要传递变量,但是又不能使用Session、Cookie、Application,您有几种方法进行处理? 答 : this.Server.Transfer 7.请编程遍历页面上所有TextBox控件并给它赋string.Empty? 答: foreach (System.Windows.Forms.Control control in this.Controls) { if (control is System.Windows.Forms.TextBox) { System.Windows.Forms.TextBox tb = (System.Windows.Forms.TextBox)control ; tb.Text = String.Empty ; } } 8.请编程实现一个冒泡排序算法? 答: int [] array = new int ; int temp = 0 ; for (int i = 0 ; i < array.Length - 1 ; i++) { for (int j = i + 1 ; j < array.Length ; j++) { if (array[j] < array) { temp = array ; array = array[j] ; array[j] = temp ; } } } 9.描述一下C#中索引器的实现过程,是否只能根据数字进行索引? 答:不是。可以用任意类型。 10.求以下表达式的,写出您想到的一种或几种实现方法: 1-2+3-4+……+m [Page] 答: int Num = this.TextBox1.Text.ToString() ; int Sum = 0 ; for (int i = 0 ; i < Num + 1 ; i++) { if((i%2) == 1) { Sum += i ; } else { Sum = Sum - I ; } } System.Console.WriteLine(Sum.ToString()); System.Console.ReadLine() ; 11.用.net做B/S结构的系统,您是用几层结构来开发,每一层之间的关系以及为什么要这样分层? 答:一般为3层 数据访问层,业务层,表示层。 数据访问层对数据库进行增删查改。 业务层一般分为二层,业务表观层实现与表示层的沟通,业务规则层实现用户密码的安全等。 表示层为了与用户交互例如用户添加表单。 优点: 分工明确,条理清晰,易于调试,而且具有可扩展性。 缺点: 增加成本。 12.在下面的例子里 using Sy

110,545

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 C#
社区管理员
  • C#
  • Web++
  • by_封爱
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

让您成为最强悍的C#开发者

试试用AI创作助手写篇文章吧