Функция идентификации LINQ?
Просто немного обманываем синтаксис LINQ. Я выравниваю IEnumerable<IEnumerable<T>>
с помощью SelectMany(x => x)
.
Моя проблема связана с выражением лямбда x => x
. Это выглядит немного уродливо. Есть ли какой-то статический объект "identity function", который я могу использовать вместо x => x
? Что-то вроде SelectMany(IdentityFunction)
?
Ответы
Ответ 1
Примечание: этот ответ был верным для С# 3, но в какой-то момент (С# 4? С# 5?) тип вывода улучшился, так что метод IdentityFunction
, показанный ниже, может быть легко использован.
Нет, нет. Это должно было быть общим, для начала:
public static Func<T, T> IdentityFunction<T>()
{
return x => x;
}
Но тогда вывод типа не будет работать, поэтому вам придется делать:
SelectMany(Helpers.IdentityFunction<Foo>())
который намного уродливее, чем x => x
.
Другая возможность заключается в том, что вы переносите это в метод расширения:
public static IEnumerable<T> Flatten<T>
(this IEnumerable<IEnumerable<T>> source)
{
return source.SelectMany(x => x);
}
К сожалению, с общей дисперсией, как это бывает, что вполне может быть нарушено в различных случаях в С# 3... например, оно не применимо к List<List<string>>
. Вы можете сделать его более общим:
public static IEnumerable<TElement> Flatten<TElement, TWrapper>
(this IEnumerable<TWrapper> source) where TWrapper : IEnumerable<TElement>
{
return source.SelectMany(x => x);
}
Но опять же, у вас возникли проблемы с типом вывода, я подозреваю...
EDIT: чтобы ответить на комментарии... да, С# 4 делает это проще. Вернее, он делает первый метод Flatten
более полезным, чем в С# 3. Вот пример, который работает в С# 4, но не работает в С# 3, потому что компилятор не может преобразовать из List<List<string>>
в IEnumerable<IEnumerable<string>>
:
using System;
using System.Collections.Generic;
using System.Linq;
public static class Extensions
{
public static IEnumerable<T> Flatten<T>
(this IEnumerable<IEnumerable<T>> source)
{
return source.SelectMany(x => x);
}
}
class Test
{
static void Main()
{
List<List<string>> strings = new List<List<string>>
{
new List<string> { "x", "y", "z" },
new List<string> { "0", "1", "2" }
};
foreach (string x in strings.Flatten())
{
Console.WriteLine(x);
}
}
}
Ответ 2
Если я не понял этот вопрос, кажется, что в С# 4 работает отлично:
public static class Defines
{
public static T Identity<T>(T pValue)
{
return pValue;
}
...
Затем вы можете сделать следующее в своем примере:
var result =
enumerableOfEnumerables
.SelectMany(Defines.Identity);
Также, используя Defines.Identity
, вы можете использовать лямбду, которая выглядит как x => x
.
Ответ 3
Это работает так, как вы хотите? Я понимаю, что Jon опубликовал версию этого решения, но у него есть второй параметр типа, который необходим только в том случае, если результирующий тип последовательности отличается от типа исходной последовательности.
public static IEnumerable<T> Flatten<T>(this IEnumerable<T> source)
where T : IEnumerable<T>
{
return source.SelectMany(item => item);
}
Ответ 4
Вы можете приблизиться к тому, что вам нужно. Вместо обычной статической функции рассмотрите метод расширения для вашего IEnumerable<T>
, как если бы функция идентификации была из коллекции, а не типа (коллекция может генерировать идентификационную функцию своих элементов):
public static Func<T, T> IdentityFunction<T>(this IEnumerable<T> enumerable)
{
return x => x;
}
с этим, вам не нужно снова указывать тип и писать:
IEnumerable<IEnumerable<T>> deepList = ... ;
var flat = deepList.SelectMany(deepList.IdentityFunction());
Это немного оскорбительно, и я, вероятно, поеду с x=>x
. Кроме того, вы не можете использовать его свободно (в цепочке), поэтому он не всегда будет полезен.
Ответ 5
С С# 6.0 и если вы ссылаетесь на FSharp.Core, вы можете сделать:
using static Microsoft.FSharp.Core.Operators
И тогда ваш свободный делать:
SelectMany(Identity)
Ответ 6
С С# 6.0 все становится лучше. Мы можем определить функцию Identity способом, предложенным @Sahuagin:
static class Functions
{
public static T It<T>(T item) => item;
}
а затем используйте его в SelectMany
конструкторе using static
:
using Functions;
...
var result = enumerableOfEnumerables.SelectMany(It);
Я думаю, что это выглядит очень лаконично. Я также нашел функцию Identity полезной при создании словарей:
class P
{
P(int id, string name) // sad, we are not getting Primary Constructors in C# 6.0
{
ID = id;
Name = id;
}
int ID { get; }
int Name { get; }
static void Main(string[] args)
{
var items = new[] { new P(1, "Jack"), new P(2, "Jill"), new P(3, "Peter") };
var dict = items.ToDictionary(x => x.ID, It);
}
}
Ответ 7
Я бы пошел с простым классом с единственным статическим свойством и добавил столько, сколько нужно по строке
internal class IdentityFunction<TSource>
{
public static Func<TSource, TSource> Instance
{
get { return x => x; }
}
}
SelectMany(IdentityFunction<Foo>.Instance)