zoukankan      html  css  js  c++  java
  • c# 对象相等性和同一性

    一:对象相等性和同一性

    System.Object提供了名为Equals的虚方法,作用是在两个对象包含相同值的前提下返回true,内部实现

    public class Object
        {
            public virtual Boolean Equals(Object obj)
            {
                //比较两个引用指向同一个对象,他们肯定包含相同的值
                if (this == obj) return true;
    
                //假定对象不包含相同的值
                return false;
            }
        }

    乍一看,这似乎就是Euqals的合理实现,假如this和obj实参引用同一个对象,就返回true,似乎合理是因为Equals知道对象

    肯定包含和它一样的值,但假如实参引用不同对象,Equals就不肯定对象是否包含相同的值,所以返回false,换言之,

    对于Object的Equals方法的默认实现:它实现的实际是同一性,而非相等性。

    所以理想的实现应该是下面这样:

    public class Object
        {
            public virtual Boolean Equals(object obj)
            {
                //要比较的对象不能为空
                if (obj == null) return false;
    
                //如果对象属于不同类型,则肯定不相等
                if (this.GetType() != obj.GetType()) return false;
    
                //如果对象属于相同的类型,那么在它们所有字段都匹配的前提下返回true
                //由于System.Object没有定义任何字段,所有字段是匹配的
                return true;
            }
        }

     类型重写Equals方法时应调用其基类的Equals实现(除非基类就是Object),另外,由于类型能够重写Object的Euqals方法,

    所以不能再用它来测试同一性。

    为了解决这个问题,Object 提供了静态方法ReferenceEquals,其原型如下:

    public class Object
    {
         pubic static Boolean ReferenceEquals(Object objA, Object objB)
        {
              return (objA == objB)  
        }  
    }

    检查同一性(看两个引用是否指向同一个对象)务必调用ReferenceEquals

    不应该使用C#的==操作符(除非先把这两个操作数都转型为Object),因为某个操作数的类型可能重载了==操作符,

    为其赋予不同于“同一性”的语义。

    可以看到,在涉及对象相等性和同一性的时候,.NET Framework 的设计很容易使人混淆。

    System.ValueType(所有值类型的基类)就重写了Object的Equals方法,并进行了正确的实现来执行值的相等性检查(而不是同一性检查)。

    内部实现:

    [SecuritySafeCritical]
        [__DynamicallyInvokable]
        public override bool Equals(object obj)
        {
          if (obj == null)
            return false;
          RuntimeType type = (RuntimeType) this.GetType();
          if ((RuntimeType) obj.GetType() != type)
            return false;
          object a = (object) this;
          if (ValueType.CanCompareBits((object) this))
            return ValueType.FastEqualsCheck(a, obj);
          FieldInfo[] fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
    //比较类型的每个实例字段,任何字段不相等,都返回false
    for (int index = 0; index < fields.Length; ++index) { object obj1 = ((RtFieldInfo) fields[index]).UnsafeGetValue(a); object obj2 = ((RtFieldInfo) fields[index]).UnsafeGetValue(obj); if (obj1 == null) { if (obj2 != null) return false; } else if (!obj1.Equals(obj2)) return false; } return true; }

    可以看到,ValueType的Equals方法利用反射,由于反射机制慢的原因,应该定义自己的值类型时重写Equals方法来提供

    自己的实现,从而提高值类型相等性比较的性能。重写Equals时,可能还需要实现一下方法:

    让类型实现System.IEquatable<T>接口的Equals方法

    这个泛型接口允许定义类型安全的Equals方法

    重载==和!=操作符方法

    通常应实现这些操作符方法,在内部调用类型安全的Equals

    如果出于排序的目的而比较类型的实例,类型还应实现System.IComparable的CompareTo方法和System.IComparable<T>类型

    安全的CompareTo,如果实现了这些方法,还可考虑重载各种比较操作符方法(<,<=,>,>=),这些方法内部调用类型安全的CompareTo。

    对于值类型来说,经过上面讨论,总结以下的一些规范:

    1:要为值类型是实现Equatable<T>

    2:要在实现IEquatable<T>的同时覆盖Object.Equals

    //Equals的这两个重载方法因该具有完全相同的语义
        public struct PositiveInt32 : IEquatable<PositiveInt32>
        {
            public bool Equals(PositiveInt32 other)
            {
    
            }
            public override bool Equals(object obj)
            {
                if (!(obj is PositiveInt32)) return false;
                return Equals((PositiveInt32)obj);
            }
        }
    View Code

    3:考虑在实现 IEquatable<T>的同时重载 operator==和 operator!=

     public struct Decimal : IEquatable<Decimal>
        {
            public bool Equals(Decimal other)
            {
    
            }
    
            public static bool operator ==(Decimal x, Decimal y)
            {
                return x.Equals(y);
            }
    
            public static bool operator !=(Decimal x, Decimal y)
            {
                return !x.Equals(y);
            }
        }
    View Code

    4:要在实现 IComparable<T>的同时实现IEquatable<T>
    注意,由于并非所有的类型都支持排序,因此本条规范反过来并不成立。

    public struct Decimal : IComparable<Decimal>, IEquatable<Decimal> { ...}

    5:考虑在实现 IComparable<T>的同时重载比较操作符(<、>、<=、>=)

     public struct Decimal : IComparable<Decimal>
        {
            public int CompareTo(Decimal other) { ...}
    
            public static bool operator < (Decimal x, Decimal y)
            {
                return x.CompareTo(y) < 0;
            }
    
            public static bool operator >(Decimal x, Decimal y)
            {
                return x.CompareTo(y) > 0;
            } 
        }
    View Code

     对于引用类型来说,总结以下的一些规范:

    1:考虑覆盖Equals以提供值相等语义——如果引用类型表示的是一个值。例如,对那些表示数值或其他数学实体的引用类型来说,

          可以考虑覆盖Equa1s方法。

    2:不要为可变的引用类型实现值相等语义。实现值相等语义的引用类型应该是不可变的(比如 System. String)。

          例如,对具备值相等语义的可变引用类型来说,当它们的值改变时(因此散列码也发生改变),它们可能会在散列表中“丢失”。

    二:区别对待==和Equals

    无论是操作符“==”还是方法“equals”,都倾向于表达这样一个原则

    对于值类型,如果类型的值相等,就应该返回True。

    对于引用类型,如果类型指向同一个对象,则返回True。

    操作符“==”和“ Equals”方法都是可以被重载的

    有些引用类型对默认实现进行了覆盖,以提供值相等的语义,例如,由于字符串的值是由字符串中的字符决定的,

    因此任何两个字符串实例只要包含完全相同的字符排列,String类的Equals方法就会返回true。

    在现实生活中,如果两者的 IDCode是相等的,我们就认为两者是同一个人,这个时候,就要重载 Equals这个方法,代码如下所示:

    class Person
        {
            public string IDCode { get; private set; }
    
            public Person(string idCode)
            {
                this.IDCode = idCode;
            }
    
            public override bool Equals(object obj)
            {
                Person p = obj as Person;
                return this.IDCode == p.IDCode;
            }
    
            /*这里,不要像下面这样再定义操作符“==”和“!=”的重载。
             * 一般来说,对于引用类型,我们要定义“值相等性”,应该仅仅去重载 Equals方法,同时让“=”表示“引用相等性”。*/ 
            public static bool operator ==(Person p1, Person p2)
            {
                return p1.IDCode == p2.IDCode;
            }
    
            public static bool operator !=(Person p1, Person p2)
            {
                return p1.IDCode != p2.IDCode;
            }
        }

     三:实现对象比较器

    上面提到System.IComparable的CompareTo方法实现比较,下面看一下如何实现,

    class Salary : IComparable<Salary>
        {
            public string Name { get; set; }
    
            public int BaseSalary { get; set; }
    
            public int Bonus { get; set; }
    
            public int CompareTo(Salary other)
            {
                return this.BaseSalary.CompareTo(other.BaseSalary);
            }
        }

    实现了IComparable后,我们就可以根据BaseSalary对Salary进行排序了,

    class Program
        {
            static void Main(string[] args)
            {
                List<Salary> lists = new List<Salary>()
                {
                    new Salary(){ Name = "Mike", BaseSalary = 2000, Bonus = 500},
                    new Salary(){ Name = "Rose", BaseSalary = 1000, Bonus = 400},
                    new Salary(){ Name = "Jeffry", BaseSalary = 3000, Bonus = 600}
                };
    
                lists.Sort();
            }
        }

    如果想以Bonus进行排序,除了修改上面的程序,还可以使用IComparer实现自定义比较器,

    class Salary : IComparer<Salary>
        {
            public string Name { get; set; }
    
            public int BaseSalary { get; set; }
    
            public int Bonus { get; set; }
    
            public int Compare(Salary x, Salary y)
            {
                return x.Bonus.CompareTo(y.Bonus);
            }
        }

    实现了IComparer后,我们就可以根据Bonus对Salary进行排序了,

    class Program
        {
            static void Main(string[] args)
            {
                List<Salary> lists = new List<Salary>()
                {
                    new Salary(){ Name = "Mike", BaseSalary = 2000, Bonus = 500},
                    new Salary(){ Name = "Rose", BaseSalary = 1000, Bonus = 400},
                    new Salary(){ Name = "Jeffry", BaseSalary = 3000, Bonus = 600}
                };
    
                lists.Sort(new BonusComparer());
            }
        }

    Sort默认是正序排序,如果想倒叙排序,只需在返回前加负号即可,因为CompareTo方法返回的是-1,0,1。

    return -x.Bonus.CompareTo(y.Bonus);

    如果是类似List<string>或者List<int>进行排序:

    正序:list.Sort();

    倒叙:list.Sort((x,y) => -x.CompareTo(y));

    另外,对集合的排序还可以用集合的扩展方法OrderBy或OrderByDescending实现

  • 相关阅读:
    OutputCache 缓存key的创建 CreateOutputCachedItemKey
    Asp.net Web Api源码调试
    asp.net mvc源码分析DefaultModelBinder 自定义的普通数据类型的绑定和验证
    Asp.net web Api源码分析HttpParameterBinding
    Asp.net web Api源码分析HttpRequestMessage的创建
    asp.net mvc源码分析ActionResult篇 RazorView.RenderView
    Asp.Net MVC 项目预编译 View
    Asp.net Web.config文件读取路径你真的清楚吗?
    asp.net 动态创建TextBox控件 如何加载状态信息
    asp.net mvc源码分析BeginForm方法 和ClientValidationEnabled 属性
  • 原文地址:https://www.cnblogs.com/SimplePoint/p/11651231.html
Copyright © 2011-2022 走看看