Що таке 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;. =)
Арк-кун

1
Це особливий метод розширення.
Брайан Расмуссен

Єдине особливе у цьому - Extensionатрибут, який читають лише компілятори C # / VB.Net, а не CLR. В основному методи екземпляра (на відміну від статичних функцій) мають прихований 0-й параметр "цей". Отже, метод 1 екземпляра аргументу дуже схожий на 2-аргументальну статичну функцію. Потім у нас є делегати, які зберігають цільовий об’єкт і вказівник функції . Делегати можуть зберігати перший аргумент у цілі або не робити цього.
Арк-кун

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)));

тощо.

Пам’ятайте, що це делегат, для більш розширеної інформації зверніться до документації.


1
Відмінна відповідь, але для складання ключових слів статичні
потрібні

16

Мені здається Func<T>дуже корисним, коли я створюю компонент, який потрібно персоналізувати "на льоту".

Візьмемо цей дуже простий приклад: PrintListToConsole<T>компонент.

Дуже простий об’єкт, який друкує цей список об’єктів на консолі. Ви хочете дозволити розробнику, який його використовує, персоналізувати вихід.

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

Без функц

По-перше, ви повинні створити інтерфейс для класу, який приймає вхід і виробляє рядок для друку на консолі.

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
Дякуємо за публікацію цього Марко. Це мені дуже допомогло. Я деякий час намагаюся зрозуміти функцію, а також активно використовую її у своєму програмуванні. Цей приклад очистить шлях. Мені довелося додати метод 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...>який є тим самим, але повертає недійсним.


0

Можливо, ще не пізно додати трохи інформації.

Сума:

Функція - це спеціальний делегат, визначений у системному просторі імен, який дозволяє вказувати на метод із тією ж підписом (як це роблять делегати), використовуючи 0-16 вхідних параметрів і повинен щось повертати.

Номенклатура та використання2:

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

Визначення:

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

Де він використовується:

Він використовується в лямбда-виразах та анонімних методах.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.