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实现

  • 相关阅读:
    Stm32cubemx_v6-1-1 提示需要JDK8版本,但已经安装JDK11 exe4j
    [Linux 内核驱动开发] 根据设备寻找驱动等信息
    DNS/mDNS/DoH/DoT 等DNS协议概括
    常用的在线工具网站
    计算机学科名词解析:透明
    Makefile 的用处,解决已包含头文件但还是 undefined reference to
    Oracle DataBases 12C Realeased2
    jz2440 开发板玩法
    树莓派 Zero W 安装与内核驱动开发入门
    深度学习与机器人结合 帮你做家务
  • 原文地址:https://www.cnblogs.com/SimplePoint/p/11651231.html
Copyright © 2011-2022 走看看