Чтобы (общий) метод .Except()
LINQ работал, два перечислимых значения (IEnumerable<T>
), передаваемых в качестве аргументов, должны:
- перечислять экземпляры одного типа
T
- , и этот тип должен реализовывать интерфейс
IEquatable<T>
.
PowerShell, по-видимому, не может найти нужную перегрузку для .Except()
с[object[]]
массивы, возвращаемые $Table1.FindName1
и $Table2.FindName1
, хотя эти массивы технически соответствуют вышеуказанным требованиям - я не знаю почему.
Однако, просто приведение этих массивов к тому, что уже есть - [object[]]
- решает проблему:
[Linq.Enumerable]::Except([object[]] $Table1.FindName1, [object[]] $Table2.FindName1)
Учитывая, что столбец .FindName1
в конечном итоге содержит строк , вы также можете привести к [string[]]
, хотя в моих неофициальных тестах это не так.не предлагайте производительность снова, по крайней мере, с вашими примерами данных.
Теперь, если вы хотите вернуть целых строк при использовании столбца .FindName1
только для сравнение , все становится намного сложнее:
Необходимо реализовать пользовательский класс сравнения, который реализует интерфейс IEqualityComparer[T]
.
Необходимо преобразовать коллекцию .Rows
таблиц данных в IEnumerable[DataRow]
, что требуетвызов метода System.Linq.Enumerable.Cast () через отражение .
- Примечание. Хотя вы можете напрямую привести к
[DataRow[]]
, это приведет к неэффективному преобразованию строкколлекция в массив.
Вот решение PSv5 +, которое реализует пользовательский класс сравнения в качестве класса PowerShell:
# A custom comparer class that compares two DataRow instances by their
# .FindName1 column.
class CustomTableComparer : Collections.Generic.IEqualityComparer[Data.DataRow] {
[bool] Equals([Data.DataRow] $x, [Data.DataRow] $y) {
return [string]::Equals($x.FindName1, $y.FindName1, 'Ordinal')
}
[int] GetHashCode([Data.DataRow] $row) {
# Note: Any two rows for which Equals() returns $true must return the same
# hash code. Because *ordinal, case-sensitive* string comparison is
# used above, it's sufficient to simply call .GetHashCode() on
# the .FindName1 property value, but that would have to be tweaked
# for other types of string comparisons.
return $row.FindName1.GetHashCode();
}
}
# Use reflection to get a reference to a .Cast() method instantiation
# that casts to IEnumerable<DataRow>.
$toIEnumerable = [Linq.Enumerable].GetMethod('Cast').MakeGenericMethod([Data.DataRow])
# Call .Except() with the casts and the custom comparer.
# Note the need to wrap the .Rows value in an aux. single-element
# array - (, ...) - for it to be treated as a single argument.
[Linq.Enumerable]::Except(
$toIEnumerable.Invoke($null, (, $Table1.Rows)),
$toIEnumerable.Invoke($null, (, $Table2.Rows)),
[CustomTableComparer]::new()
)
Эта проблема GitHub предлагает сделать LINQ первоклассным гражданином PowerShell.