Подтвердить что ты не робот

Функция идентификации LINQ?

Просто немного обманываем синтаксис LINQ. Я выравниваю IEnumerable<IEnumerable<T>> с помощью SelectMany(x => x).

Моя проблема связана с выражением лямбда x => x. Это выглядит немного уродливо. Есть ли какой-то статический объект "identity function", который я могу использовать вместо x => x? Что-то вроде SelectMany(IdentityFunction)?

4b9b3361

Ответ 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)