2015-12-02 4 views
2

Перед маркировки это как дубликат из ее названия, пожалуйста, рассмотрим следующую короткую программу:сравнения, если две последовательности равны

static void Main() 
{ 
    var expected = new List<long[]> { new[] { Convert.ToInt64(1), Convert.ToInt64(999999) } }; 
    var actual = DoSomething(); 
    if (!actual.SequenceEqual(expected)) throw new Exception(); 
} 

static IEnumerable<long[]> DoSomething() 
{ 
    yield return new[] { Convert.ToInt64(1), Convert.ToInt64(999999) }; 
} 

У меня есть метод, который возвращает последовательность массивов типа длиной. Чтобы проверить это, я написал некоторый тестовый код, подобный этому, в пределах Main.

Однако я получаю исключение, но я не знаю, почему. Должна ли ожидаемая последовательность быть сопоставимой с фактически возвращенной или я ничего не пропустил?

Мне кажется, что и метод, и epxected содержат ровно один единственный элемент, содержащий массив типа long, не так ли?

EDIT: Как я могу достичь, чтобы не получить значение исключения для сравнения элементов в перечислении, чтобы вернуть равенство?

+1

Что такое исключение? – Magnus

+0

Тот, кого я бросаю? – HimBromBeere

+2

Элементы в вашей последовательности: 'long []'. Это будет сравнение ссылок на массивы, которые действительно отличаются друг от друга. Элементы массива (который находится в последовательности) не будут сравниваться. –

ответ

5

Реальная проблема заключается в том, что вы сравниваете два long[] и Enumerable.SequenceEquals будет использовать ObjectEqualityComparer<Int64[]> (вы можете увидеть, что при рассмотрении EqualityComparer<long[]>.Default, который является то, что в настоящее время внутренне используется Enumerable.SequenceEquals), которая будет сравнивать ссылки тех, два массивы, а не фактические значения , хранящиеся внутри массива, которые, очевидно, не совпадают.

Чтобы обойти эту проблему, можно написать пользовательскую EqualityComparer<long[]>:

static void Main() 
{ 
    var expected = new List<long[]> 
         { new[] { Convert.ToInt64(1), Convert.ToInt64(999999) } }; 
    var actual = DoSomething(); 

    if (!actual.SequenceEqual(expected, new LongArrayComparer())) 
     throw new Exception(); 
} 

public class LongArrayComparer : EqualityComparer<long[]> 
{ 
    public override bool Equals(long[] first, long[] second) 
    { 
     return first.SequenceEqual(second); 
    } 

    // GetHashCode implementation in the courtesy of @JonSkeet 
    // from http://stackoverflow.com/questions/7244699/gethashcode-on-byte-array 
    public override int GetHashCode(long[] arr) 
    { 
     unchecked 
     { 
      if (array == null) 
      { 
       return 0; 
      } 

      int hash = 17; 
      foreach (long element in arr) 
      { 
       hash = hash * 31 + element.GetHashCode(); 
      } 

      return hash; 
     } 
    } 
} 
+1

Неверный код 'GetHashCode'. Это будет хорошо работать с этим конкретным использованием, потому что 'SequenceEqual' не использует' GetHashCode', но если кто-то использовал это для другого использования 'EqualityComparer ' like 'Distinct', это было бы неправильно. Вам нужно создать хеш-код, относящийся к равенству последовательности. –

+0

@JonHanna Вы правы, это был быстрый хак, чтобы показать, что можно легко реализовать пользовательский сопоставитель. Я исправлю это. –

+0

Какая удача я реализовал общий «EqualityComparer» несколько месяцев назад, тогда как я могу использовать лямбда-выражения для реализации этих двух методов. Однако я в принципе буду использовать именно этот подход, спасибо. – HimBromBeere

0

SequenceEquals тесты для идентичных элементов в пределах последовательностей. Элементы в перечислениях имеют тип long[], поэтому мы фактически сравниваем два разных массива (содержащих одни и те же элементы) друг против друга, что было бы наглядно сделано путем сравнения их ссылок вместо их фактического значения.

Так что мы на самом деле проверить здесь это expected[0] == actual[0] вместо expected[0].SequqnceEquals(actual[0])

Это obiosuly возвращает false в оба массива разделяют разные ссылки.

Если придавить иерархия с помощью SelectMany мы получаем то, что мы хотим:

if (!actual.SelectMany(x => x).SequenceEqual(expected.SelectMany(x => x))) throw new Exception(); 

EDIT:

На основе this approach я нашел еще один элегантный способ проверить, если все элементы из expected содержатся в actual также:

if (!expected.All(x => actual.Any(y => y.SequenceEqual(x)))) throw new Exception(); 

Это будет искать, если вечный подсчет в пределах expected есть список в пределах actual, который последовательно идентичен текущему. Это кажется гораздо более умным, так как нам не нужны никакие пользовательские EqualityComparer и не странная реализация hashcode.

4

Нет, ваши последовательности: не равно!

Позволяет удалить битовую последовательность, и просто взять то, что в первого элемента каждого элемента

var firstExpected = new[] { Convert.ToInt64(1), Convert.ToInt64(999999) }; 
var firstActual = new[] { Convert.ToInt64(1), Convert.ToInt64(999999) }; 
Console.WriteLine(firstExpected == firstActual); // writes "false" 

Код выше сравнения двух отдельных массивов равенства. Равенство не проверяет содержимое массивов, проверяет ссылки на равенство.

Ваш код с использованием SequenceEquals есть, по существу, тот же предмет. Он проверяет ссылки в каждом случае каждого элемента в перечислимом.

+0

Yeap, на самом деле нашел это за секунду назад. Однако мое решение на самом деле не кажется мне удобным, у вас есть лучший? – HimBromBeere

+0

@ Ответ Юваля содержит правильный путь. Нет смысла переписывать это. – Jamiec