Что такое Func, как и когда он используется

115

Что это такое Func<>и для чего он нужен?

обучение
источник
4
Это просто ярлык для делегатов с определенной подписью. Чтобы полностью понять приведенные ниже ответы, вам необходимо понять делегатов ;-)
Тео Ленндорф,
2
В ответе @Oded написаноIf you have a function that needs to return different types, depending on the parameters, you can use a Func delegate, specifying the return type.
LCJ

Ответы:

76

Func<T>- это предопределенный тип делегата для метода, который возвращает некоторое значение типа T.

Другими словами, вы можете использовать этот тип для ссылки на метод, который возвращает некоторое значение T. Например

public static string GetMessage() { return "Hello world"; }

на это можно ссылаться

Func<string> f = GetMessage;
Брайан Расмуссен
источник
Но он также может представлять статическую функцию с одним аргументом =)
Арк-кун
2
@ Арк-кун, нет, это не так. Определение Func<T>есть delegate TResult Func<out TResult>(). Никаких аргументов. Func<T1, T2>будет функцией, которая принимает один аргумент.
Брайан Расмуссен
4
Нет, я прав. static int OneArgFunc(this string i) { return 42; } Func<int> f = "foo".OneArgFunc;. =)
Ark-kun
1
Это особенный метод расширения.
Брайан Расмуссен
Единственная особенность этого Extensionатрибута - это атрибут, который читается только компиляторами C # / VB.Net, а не CLR. По сути, методы экземпляра (в отличие от статических функций) имеют скрытый 0-й параметр this. Итак, метод экземпляра с одним аргументом очень похож на статическую функцию с двумя аргументами. Затем у нас есть делегаты, которые хранят целевой объект и указатель на функцию . Делегаты могут хранить первый аргумент в target или не делать этого.
Ark-kun
87

Думайте об этом как о заполнителе. Это может быть весьма полезно, если у вас есть код, который следует определенному шаблону, но не должен быть привязан к какой-либо конкретной функции.

Например, рассмотрим Enumerable.Selectметод расширения.

  • Шаблон является: для каждого элемента в последовательности, выберите некоторое значение из этого элемента (например, свойства) и создать новую последовательность , состоящую из этих значений.
  • Заполнитель является: некоторая функция селектора , который на самом деле получает значения для последовательности описанной выше.

Этот метод принимает Func<T, TResult>вместо какой-либо конкретной функции. Это позволяет использовать его в любом контексте, где применяется вышеуказанный шаблон.

Так, например, скажем, у меня есть List<Person>и мне нужно только имя каждого человека в списке. Я могу это сделать:

var names = people.Select(p => p.Name);

Или скажите, что я хочу возраст каждого человека:

var ages = people.Select(p => p.Age);

Сразу видно, как мне удалось использовать один и тот же код, представляющий шаблонSelect) с двумя разными функциями ( p => p.Nameи p => p.Age).

Альтернативой может быть написание новой версии Selectкаждого случая, когда вы хотите сканировать последовательность для другого типа значения. Итак, чтобы добиться того же эффекта, что и выше, мне понадобятся:

// Presumably, the code inside these two methods would look almost identical;
// the only difference would be the part that actually selects a value
// based on a Person.
var names = GetPersonNames(people);
var ages = GetPersonAges(people);

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

Дэн Тао
источник
66

Func<T1, T2, ..., Tn, Tr> представляет функцию, которая принимает (T1, T2, ..., Tn) аргументов и возвращает Tr.

Например, если у вас есть функция:

double sqr(double x) { return x * x; }

Вы можете сохранить его как некую функцию-переменную:

Func<double, double> f1 = sqr;
Func<double, double> f2 = x => x * x;

А затем используйте точно так же, как если бы вы использовали sqr:

f1(2);
Console.WriteLine(f2(f1(4)));

и т.п.

Однако помните, что это делегат, для получения более подробной информации обратитесь к документации.

Grozz
источник
1
Отличный ответ, но для составления статического ключевого слова необходим
boctulus
16

Я считаю Func<T>очень полезным, когда создаю компонент, который нужно персонализировать «на лету».

Возьмем этот очень простой пример: PrintListToConsole<T>компонент.

Очень простой объект, который выводит этот список объектов на консоль. Вы хотите позволить разработчику, который его использует, персонализировать вывод.

Например, вы хотите позволить ему определять определенный тип числового формата и так далее.

Без Func

Во-первых, вы должны создать интерфейс для класса, который принимает входные данные и создает строку для вывода на консоль.

interface PrintListConsoleRender<T> {
  String Render(T input);
}

Затем вам нужно создать класс, PrintListToConsole<T>который берет ранее созданный интерфейс и использует его для каждого элемента списка.

class PrintListToConsole<T> {

    private PrintListConsoleRender<T> _renderer;

    public void SetRenderer(PrintListConsoleRender<T> r) {
        // this is the point where I can personalize the render mechanism
        _renderer = r;
    }

    public void PrintToConsole(List<T> list) {
        foreach (var item in list) {
            Console.Write(_renderer.Render(item));
        }
    }   
}

Разработчик, которому необходимо использовать ваш компонент, должен:

  1. реализовать интерфейс

  2. передать настоящий класс PrintListToConsole

    class MyRenderer : PrintListConsoleRender<int> {
        public String Render(int input) {
            return "Number: " + input;
        }
    }
    
    class Program {
        static void Main(string[] args) {
            var list = new List<int> { 1, 2, 3 };
            var printer = new PrintListToConsole<int>();
            printer.SetRenderer(new MyRenderer());
            printer.PrintToConsole(list);
            string result = Console.ReadLine();   
        }   
    }

Используя Func, это намного проще

Внутри компонента вы определяете параметр типа, Func<T,String>который представляет интерфейс функции, которая принимает входной параметр типа T и возвращает строку (вывод для консоли)

class PrintListToConsole<T> {

    private Func<T, String> _renderFunc;

    public void SetRenderFunc(Func<T, String> r) {
        // this is the point where I can set the render mechanism
        _renderFunc = r;
    }

    public void Print(List<T> list) {
        foreach (var item in list) {
            Console.Write(_renderFunc(item));
        }
    }
}

Когда разработчик использует ваш компонент, он просто передает компоненту реализацию Func<T, String>типа, то есть функцию, которая создает вывод для консоли.

class Program {
    static void Main(string[] args) {
        var list = new List<int> { 1, 2, 3 }; // should be a list as the method signature expects
        var printer = new PrintListToConsole<int>();
        printer.SetRenderFunc((o) => "Number:" + o);
        printer.Print(list); 
        string result = Console.ReadLine();
    }
}

Func<T>позволяет определять интерфейс универсального метода на лету. Вы определяете, к какому типу относятся входные и выходные данные. Просто и лаконично.

Марко Стаффоли
источник
2
Спасибо, что разместили это, Марко. Мне это действительно помогло. Некоторое время я пытался понять func, а также активно использую его в своем программировании. Этот пример очистит путь. Мне пришлось добавить метод StampaFunc, поскольку он был исключен в исходном коде, что не позволяло ему отображаться.
Siwoku Adeola
1
Я думаю, что в примере Func пропущена строка. Где вызов функции печати или StampaFunc?
Башар Абу Шамаа
11

Func<T1,R>и другие предопределенные общие Funcделегаты ( Func<T1,T2,R>, Func<T1,T2,T3,R>и другие) являются общими делегатами , которые возвращают тип последнего родового параметра.

Если у вас есть функция, которая должна возвращать разные типы в зависимости от параметров, вы можете использовать Funcделегат, указав тип возвращаемого значения.

Одед
источник
7

Это просто предопределенный универсальный делегат. Используя его, вам не нужно объявлять каждого делегата. Есть еще один предопределенный делегат, Action<T, T2...>который такой же, но возвращает void.

Стефан Штайнеггер
источник
0

Может, еще не поздно добавить инфу.

Сумма:

Func - это настраиваемый делегат, определенный в пространстве имен System, который позволяет вам указывать на метод с той же сигнатурой (что и делегаты), используя от 0 до 16 входных параметров, которые должны что-то возвращать.

Номенклатура и способы использования:

Func<input_1, input_2, ..., input1_6, output> funcDelegate = someMethod;

Определение:

public delegate TResult Func<in T, out TResult>(T arg);

Где используется:

Он используется в лямбда-выражениях и анонимных методах.

overRideKode
источник