Я нахожу Func<T>
очень полезным, когда создаю компонент, который нужно персонализировать «на лету».
Возьмем этот очень простой пример: компонент PrintListToConsole<T>
.
Очень простой объект, который печатает этот список объектов на консоли. Вы хотите, чтобы разработчик, который использует его, персонализировал вывод.
Например, вы хотите дать ему определение определенного типа номера и т. Д.
Без Func
g7]Во-первых, вам нужно создать интерфейс для класса, который берет ввод и выводит строку для печати на консоль.
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 намного проще
. Внутри компонента вы определяете параметр type 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 Array[1, 2, 3];
var printer = new PrintListToConsole<int>();
printer.SetRenderFunc((o) => "Number:" + o);
printer.Print();
string result = Console.ReadLine();
}
}
Func<T>
позволяет вам определять общий интерфейс метода «на лету». Вы определяете тип ввода и тип вывода. Простой и лаконичный.
Оператор присваивания не требуется, чтобы быть сделанным виртуальным.
обсуждение ниже - приблизительно operator=
, но оно также относится к любому оператору, перегружающемуся, который берет в рассматриваемом типе, и любая функция, которая берет в рассматриваемом типе.
ниже обсуждения показывает, что виртуальное ключевое слово не знает о наследовании параметра в отношении нахождения подписи функции соответствия. В заключительном примере это показывает, как правильно обработать присвоение при контакте с наследованными типами.
<час>Виртуальные функции не знают о наследовании параметра:
подпись функции А должна быть тем же для виртуального, чтобы сыграть роль. Таким образом даже при том, что в следующем примере, оператор = сделан виртуальным, вызов никогда не будет действовать как виртуальная функция в D, потому что параметры и возвращаемое значение оператора = отличаются.
функция B::operator=(const B& right)
и D::operator=(const D& right)
на 100% полностью отличается и рассматривается как 2 отличных функции.
class B
{
public:
virtual B& operator=(const B& right)
{
x = right.x;
return *this;
}
int x;
};
class D : public B
{
public:
virtual D& operator=(const D& right)
{
x = right.x;
y = right.y;
return *this;
}
int y;
};
<час> Значения по умолчанию и перегрузка 2 операторы:
Вы можете, хотя определяют виртуальную функцию, чтобы позволить Вам устанавливать значения по умолчанию для D, когда он присвоен переменной типа B. Это - то, даже если Ваша переменная B является действительно D, сохраненным в ссылку B. Вы не доберетесь эти D::operator=(const D& right)
функция.
В ниже случая, присвоения от 2 объектов D, хранивших в 2 ссылках B... эти D::operator=(const B& right)
, переопределение используется.
//Use same B as above
class D : public B
{
public:
virtual D& operator=(const D& right)
{
x = right.x;
y = right.y;
return *this;
}
virtual B& operator=(const B& right)
{
x = right.x;
y = 13;//Default value
return *this;
}
int y;
};
int main(int argc, char **argv)
{
D d1;
B &b1 = d1;
d1.x = 99;
d1.y = 100;
printf("d1.x d1.y %i %i\n", d1.x, d1.y);
D d2;
B &b2 = d2;
b2 = b1;
printf("d2.x d2.y %i %i\n", d2.x, d2.y);
return 0;
}
Печать:
d1.x d1.y 99 100
d2.x d2.y 99 13
, Который показывает, что D::operator=(const D& right)
никогда не используется.
Без виртуального ключевого слова на [1 110] у Вас были бы те же результаты как выше, но значение y не будет инициализировано. Т.е. это использовало бы B::operator=(const B& right)
Один последний шаг для связывания всего этого, RTTI:
можно использовать RTTI для надлежащей обработки виртуальных функций, которые берут в типе. Вот последняя часть загадки, которая выяснит, как правильно обработать присвоение при контакте с возможно наследованными типами.
virtual B& operator=(const B& right)
{
const D *pD = dynamic_cast<const D*>(&right);
if(pD)
{
x = pD->x;
y = pD->y;
}
else
{
x = right.x;
y = 13;//default value
}
return *this;
}
Оператор является методом со специальным синтаксисом. Можно рассматривать его как любой другой метод...
Это потребовало только, когда Вы хотите гарантировать, что классы, полученные из Вашего класса, добираются, все их участники скопировали правильно. Если Вы ничего не делаете с полиморфизмом, то Вы не должны действительно волноваться об этом.
я не знаю ни о чем, что препятствовало бы тому, чтобы Вы виртуализировали любой оператор, который Вы хотите - они - только вызовы метода особого случая.
Эта страница предоставляет превосходное и подробное описание того, как все это работает.
Это зависит от оператора.
точка создания виртуального оператора присваивания должна позволить Вам от преимущества способности переопределить его для копирования большего количества полей.
Поэтому, если у Вас есть Base& и у Вас на самом деле есть Derived& как динамический тип и Полученный имеет больше полей, корректные вещи копируются.
Однако существует тогда риск, что Ваш LHS является Полученным, и RHS является Основа, поэтому когда выполнения виртуального оператора в Полученном, Ваш параметр не является Полученным и у Вас нет способа вытащить поля из него.
Вот хороший discussio: http://icu-project.org/docs/papers/cpp_report/the_assignment_operator_revisited.html