Що таке Func<>
і для чого він використовується?
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.
Що таке Func<>
і для чого він використовується?
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.
Відповіді:
Func<T>
це заздалегідь визначений тип делегата для методу, який повертає деяке значення типу T
.
Іншими словами, ви можете використовувати цей тип для посилання на метод, який повертає деяке значення T
. Напр
public static string GetMessage() { return "Hello world"; }
може посилатися так
Func<string> f = GetMessage;
Func<T>
є delegate TResult Func<out TResult>()
. Аргументів немає. Func<T1, T2>
була б функцією, яка бере один аргумент.
static int OneArgFunc(this string i) { return 42; }
Func<int> f = "foo".OneArgFunc;
. =)
Extension
атрибут, який читають лише компілятори C # / VB.Net, а не CLR. В основному методи екземпляра (на відміну від статичних функцій) мають прихований 0-й параметр "цей". Отже, метод 1 екземпляра аргументу дуже схожий на 2-аргументальну статичну функцію. Потім у нас є делегати, які зберігають цільовий об’єкт і вказівник функції . Делегати можуть зберігати перший аргумент у цілі або не робити цього.
Подумайте про це як про заповнювач. Це може бути дуже корисно, якщо у вас є код, який відповідає певній схемі, але не повинен прив'язуватися до якогось конкретного функціоналу.
Наприклад, розглянемо 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);
З делегатом, який виступає в якості заповнювача, я звільняюсь від необхідності виписувати один і той же візерунок знову і знову у таких випадках.
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)));
тощо.
Пам’ятайте, що це делегат, для більш розширеної інформації зверніться до документації.
Мені здається 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));
}
}
}
Розробник, який повинен використовувати ваш компонент, повинен:
реалізувати інтерфейс
передати реальний клас до 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>
дозволяє вам визначити інтерфейс загального методу на ходу.
Ви визначаєте, яким типом є вхід і яким типом є вихід. Простий і стислий.
Func<T1,R>
та інші визначені загальні Func
делегати ( Func<T1,T2,R>
, Func<T1,T2,T3,R>
і інші) є загальними делегатами , які повертають тип останнього родового параметра.
Якщо у вас є функція, якій потрібно повертати різні типи, залежно від параметрів, ви можете використовувати Func
делегат із зазначенням типу повернення.
Це просто наперед визначений загальний делегат. Використовуючи його, вам не потрібно декларувати кожного делегата. Є ще один заздалегідь визначений делегат, Action<T, T2...>
який є тим самим, але повертає недійсним.
Можливо, ще не пізно додати трохи інформації.
Сума:
Функція - це спеціальний делегат, визначений у системному просторі імен, який дозволяє вказувати на метод із тією ж підписом (як це роблять делегати), використовуючи 0-16 вхідних параметрів і повинен щось повертати.
Номенклатура та використання2:
Func<input_1, input_2, ..., input1_6, output> funcDelegate = someMethod;
Визначення:
public delegate TResult Func<in T, out TResult>(T arg);
Де він використовується:
Він використовується в лямбда-виразах та анонімних методах.