2015-04-20 2 views
1

Я недавно наткнулся на интересную ошибку в коде сравнения, где два объекта имеют свойство равное 0.0m. Когда это свойство преобразуется в int и сравнивается, сравнение никогда не бывает равным. Воспроизводство ниже:C#: Почему следующее сравнение показывает, что 0! = 0

Возьмем абстракции А, и две реализации, В и С:

public abstract class A 
{ 
    public decimal MyProp { get; set; } 
} 

public class B : A 
{ 
} 

public class C : A 
{ 
} 

абстракции определяет несколько открытых свойств, в первую очередь, но не полностью, десятичной. Все общедоступные свойства всегда являются примитивными. Конкретные подтипы представляют эту абстракцию, полученную из двух разных источников данных. Два объекта типа A считаются равными тогда и только тогда, когда все их общедоступные свойства равны. Одна оговорка: все десятичные свойства должны быть преобразованы в int перед сравнением, используя поведение округления по умолчанию (MidpointRounding.ToEven). Это привело к следующему коду сравнения:

private static bool Compare(A a1, A a2) 
{ 
    var propertiesList = typeof(A).GetProperties(BindingFlags.Instance | BindingFlags.Public).ToList(); 
    foreach (var propertyInfo in propertiesList) 
    { 
     var value1 = propertyInfo.GetValue(a1); 
     var value2 = propertyInfo.GetValue(a2); 

     if (propertyInfo.PropertyType == typeof(decimal)) 
     { 
      value1 = Convert.ToInt32(value1); 
      value2 = Convert.ToInt32(value2); 
     } 

     // debugger confirms that value1 is 0 and value2 is 0 
     if (value1 != value2) 
     { 
      // yet these lines are always called 
      Console.WriteLine("The two A's are not equal"); 
      return false; 
     } 
    } 

    return true; 
} 

Этот код предназначен для записи таким образом, чтобы:

A1.MyProp A2.MyProp Equal? 
--------------------------------- 
0.0m   0.0m   Yes 
0.6m   1.4m   Yes 
1.5m   2.5m   Yes 
2.5m   3.5M   No 

Однако, как показано на следующее консольном приложении, первое использование корпуса (0.0m и 0.0m) всегда сбой:

private static void Main(string[] args) 
{ 
    var b = new B() { MyProp = 0.0m }; 
    var c = new C() { MyProp = 0.0m }; 

    // always false 
    var result = Compare(b, c); 
} 

Может ли кто-нибудь одолжить глаз и указать на ошибку в сравнительном коде?

+0

Я подозреваю, что это связано с поведением Convert.ToInt32 (десятичное); Сейчас я занимаюсь расследованием. –

ответ

8

Это потому, что == на object делает ссылку равенства.

Использование Equals вместо:

// debugger confirms that value1 is 0 and value2 is 0 
    if (!value1.Equals(value2)) 
    { 
     Console.WriteLine("The two A's are not equal"); 
     return false; 
    } 

Чтобы сделать нуль-безопасным вы должны также проверить null первый:

if((value1 == null && value2) != null || (value1 == null && value2 != null) || !value1.Equals(value2)) 

или как это было предложено в комментарии использование статического object.Equals:

if (!object.Equals(value1, value2)) 
    { 
     Console.WriteLine("The two A's are not equal"); 
     return false; 
    } 
+1

Еще лучше, используйте 'object.Equals (value1, value2)'. Я не вижу ничего, что исключает 'value1', возможно,« null ». – hvd

+0

Добавлен ответ. Спасибо за предложение! – MarcinJuraszek

+0

Ах, да. Пропустил автобоксинг после конверсии. Благодаря! – LiamK

1

Предлагаю использовать Использовать Equals вместо ==, так как вы имеете дело с объектами

Метод Equals является просто виртуальным, определенным в System.Object, и переопределяется тем, какие классы предпочитают это делать. Оператор == - это оператор, который может быть перегружен классами, но обычно имеет поведение идентичности.

Для ссылочных типов, где == не было перегружено, оно сравнивает, ссылаются ли на две ссылки на один и тот же объект - именно это реализует реализация Equals в System.Object.

Типы значений не обеспечивают перегрузку для == по умолчанию. Однако большинство типов значений, предоставляемых каркасом, обеспечивают их собственную перегрузку. Реализация по умолчанию Equals для типа значения предоставляется ValueType и использует отражение для сравнения, что делает ее значительно медленнее, чем обычно будет выполняться спецификация типа. Эта реализация также вызывает Equals для пар ссылок в двух сравниваемых значениях.

Однако основное различие между двумя типами сравнения при нормальном использовании (где вы вряд ли будете определять свои собственные типы значений очень часто) - это полиморфизм. Операторы перегружены, а не переопределены, что означает, что, если компилятор не знает, чтобы назвать более конкретную версию, он просто вызовет версию идентификации. Чтобы проиллюстрировать это, вот пример:

using System; 

public class Test 
{ 
    static void Main() 
    { 
     // Create two equal but distinct strings 
     string a = new string(new char[] {'h', 'e', 'l', 'l', 'o'}); 
     string b = new string(new char[] {'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 object 
     object c = a; 
     object d = b; 

     Console.WriteLine (c==d); 
     Console.WriteLine (c.Equals(d)); 
    } 
} 

Результаты:

Правда Правда Ложные Правда

0

@MarcinJuraszek является правильным, и он дает точный ответ имеет, почему != всегда возвращается true в вашем случае.

Я просто хотел перезвонить в том, что существует перегрузка оператора и что это может иметь смысл когда-то.

Образец от MSDN doc.

//add this code to class ThreeDPoint as defined previously 
// 
public static bool operator ==(ThreeDPoint a, ThreeDPoint b) 
{ 
    // If both are null, or both are same instance, return true. 
    if (System.Object.ReferenceEquals(a, b)) 
    { 
     return true; 
    } 

    // If one is null, but not both, return false. 
    if (((object)a == null) || ((object)b == null)) 
    { 
     return false; 
    } 

    // Return true if the fields match: 
    return a.x == b.x && a.y == b.y && a.z == b.z; 
} 

public static bool operator !=(ThreeDPoint a, ThreeDPoint b) 
{ 
    return !(a == b); 
}