Поиск…


замечания

Резюме

Тип делегата - это тип, представляющий конкретную подпись метода. Экземпляр этого типа относится к определенному методу с соответствующей сигнатурой. Параметры метода могут иметь типы делегирования, и поэтому этому одному методу передается ссылка на другой метод, который затем может быть вызван

Встроенные типы делегатов: Action<...> , Predicate<T> и Func<...,TResult>

System имена содержат Action<...> , Predicate<T> и Func<...,TResult> делегатов, где "..." составляет от 0 до 16 параметров универсального типа (для 0 параметров, Action являются не- родовое).

Func представляет методы с типом возвращаемого типа TResult , а Action представляет методы без возвращаемого значения (void). В обоих случаях дополнительные параметры типового типа соответствуют параметрам метода.

Predicate представляет метод с булевым типом возврата, T - входной параметр.

Пользовательские типы делегатов

Именованные типы делегатов могут быть объявлены с использованием ключевого слова delegate .

Вызов делегатов

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

Присвоение делегатам

Делегатам можно назначить следующие способы:

  • Назначение именованного метода
  • Назначение анонимного метода с использованием лямбда
  • Назначение именованного метода с использованием ключевого слова delegate .

Объединение делегатов

Несколько экземпляров делегата могут быть назначены одному экземпляру делегата с помощью оператора + . Оператор - может использоваться для удаления делегата компонента из другого делегата.

Базовые ссылки делегатов именованного метода

При назначении именованных методов делегатам они будут ссылаться на один и тот же базовый объект, если:

  • Они являются одним и тем же методом экземпляра, в том же экземпляре класса

  • Они представляют собой один и тот же статический метод для класса

    public class Greeter
    {
        public void WriteInstance()
        {
            Console.WriteLine("Instance");
        }
    
        public static void WriteStatic()
        {
            Console.WriteLine("Static");
        }
    }
    
    // ...
    
    Greeter greeter1 = new Greeter();
    Greeter greeter2 = new Greeter();
    
    Action instance1 = greeter1.WriteInstance;
    Action instance2 = greeter2.WriteInstance;
    Action instance1Again = greeter1.WriteInstance;
    
    Console.WriteLine(instance1.Equals(instance2)); // False
    Console.WriteLine(instance1.Equals(instance1Again)); // True
    
    Action @static = Greeter.WriteStatic;
    Action staticAgain = Greeter.WriteStatic;
    
    Console.WriteLine(@static.Equals(staticAgain)); // True
    

Объявление типа делегата

Следующий синтаксис создает тип delegate с именем NumberInOutDelegate , представляющий метод, который принимает int и возвращает int .

public delegate int NumberInOutDelegate(int input);

Это можно использовать следующим образом:

public static class Program
{
    static void Main()
    {
        NumberInOutDelegate square = MathDelegates.Square;
        int answer1 = square(4); 
        Console.WriteLine(answer1); // Will output 16

        NumberInOutDelegate cube = MathDelegates.Cube;
        int answer2 = cube(4);
        Console.WriteLine(answer2); // Will output 64            
    }
}

public static class MathDelegates
{
    static int Square (int x)
    {
        return x*x;
    }

    static int Cube (int x)
    {
        return x*x*x;
    }
}

example экземпляра делегата выполняется так же, как метод Square . Экземпляр делегата буквально выступает в качестве делегата для вызывающего: вызывающий вызывает делегата, а затем делегат вызывает целевой метод. Это направление отделяет вызывающего абонента от целевого метода.


Вы можете объявить общий тип делегата, и в этом случае вы можете указать, что тип является ковариантным ( out ) или контравариантным ( in ) в некоторых аргументах типа. Например:

public delegate TTo Converter<in TFrom, out TTo>(TFrom input);

Как и другие общие типы, общие типы делегатов могут иметь ограничения, например, where TFrom : struct, IConvertible where TTo : new() .

Избегайте совместной и контравариантности для типов делегатов, которые предназначены для использования для делегатов многоадресной передачи, таких как типы обработчиков событий. Это связано с тем, что конкатенация ( + ) может завершиться неудачей, если тип выполнения отличается от типа времени компиляции из-за дисперсии. Например, избегайте:

public delegate void EventHandler<in TEventArgs>(object sender, TEventArgs e);

Вместо этого используйте инвариантный общий тип:

public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e);

Также поддерживаются делегаты, где некоторые параметры изменяются с помощью ref или out , как в:

public delegate bool TryParser<T>(string input, out T result);

(пример использования TryParser<decimal> example = decimal.TryParse; ) или делегатов, где последний параметр имеет модификатор params . Типы делегатов могут иметь необязательные параметры (значения по умолчанию для поставки). Типы делегатов могут использовать типы указателей, такие как int* или char* в своих подписях или типах возврата (используйте ключевое слово unsafe ). Тип делегата и его параметры могут нести пользовательские атрибуты.

Func , Действие и предикат типы делегатов

Пространство имен System содержит Func<..., TResult> делегировать типы с 0 до 15 общих параметров, возвращая тип TResult .

private void UseFunc(Func<string> func)
{
    string output = func(); // Func with a single generic type parameter returns that type
    Console.WriteLine(output);
}

private void UseFunc(Func<int, int, string> func)
{
    string output = func(4, 2); // Func with multiple generic type parameters takes all but the first as parameters of that type
    Console.WriteLine(output);
}

Пространство имен System также содержит типы делегатов Action<...> с различным количеством общих параметров (от 0 до 16). Он похож на Func<T1, .., Tn> , но он всегда возвращает void .

private void UseAction(Action action)
{
    action(); // The non-generic Action has no parameters
}

private void UseAction(Action<int, string> action)
{
    action(4, "two"); // The generic action is invoked with parameters matching its type arguments
}

Predicate<T> также является формой Func но он всегда возвращает bool . Предикат - это способ указания пользовательских критериев. В зависимости от значения входа и логики, определенных в предикате, он вернет либо true либо false . Таким образом, Predicate<T> ведет себя так же, как Func<T, bool> и оба могут быть инициализированы и использованы одинаково.

Predicate<string> predicate = s => s.StartsWith("a");
Func<string, bool> func = s => s.StartsWith("a");

// Both of these return true
var predicateReturnsTrue = predicate("abc");
var funcReturnsTrue = func("abc");

// Both of these return false
var predicateReturnsFalse = predicate("xyz");
var funcReturnsFalse = func("xyz");

Выбор того, следует ли использовать Predicate<T> или Func<T, bool> , действительно является вопросом мнения. Predicate<T> , возможно, более выразителен в намерениях автора, тогда как Func<T, bool> , скорее всего, будет знаком с большей частью разработчиков C #.

В дополнение к этому, есть некоторые случаи, когда доступен только один из вариантов, особенно при взаимодействии с другим API. Например, List<T> и Array<T> обычно используют Predicate<T> для своих методов, в то время как большинство расширений LINQ принимают только Func<T, bool> .

Назначение именованного метода делегату

Именованные методы могут быть назначены делегатам с соответствующими сигнатурами:

public static class Example
{
    public static int AddOne(int input)
    {
        return input + 1;
    }
}


Func<int,int> addOne = Example.AddOne

Example.AddOne принимает int и возвращает int , его подпись соответствует делегату Func<int,int> . Example.AddOne может быть напрямую назначено addOne поскольку они имеют соответствующие подписи.

Равенство делегатов

Вызов .Equals() для делегата сравнивается по ссылочному равенству:

Action action1 = () => Console.WriteLine("Hello delegates");
Action action2 = () => Console.WriteLine("Hello delegates");
Action action1Again = action1;

Console.WriteLine(action1.Equals(action1)) // True
Console.WriteLine(action1.Equals(action2)) // False
Console.WriteLine(action1Again.Equals(action1)) // True

Эти правила также применяются при выполнении команды += или -= в многоадресном делетете, например, при подписке и отмене подписки на события.

Назначение делегата лямбдой

Lambdas можно использовать для создания анонимных методов для назначения делегату:

Func<int,int> addOne = x => x+1;

Обратите внимание, что явное объявление типа требуется при создании переменной таким образом:

var addOne = x => x+1; // Does not work

Передача делегатов в качестве параметров

Делегаты могут использоваться в качестве типизированных указателей функций:

class FuncAsParameters
{
  public void Run()
  {
    DoSomething(ErrorHandler1);
    DoSomething(ErrorHandler2);
  }

  public bool ErrorHandler1(string message)
  {
    Console.WriteLine(message);
    var shouldWeContinue = ...  
    return shouldWeContinue;
  }

  public bool ErrorHandler2(string message)
  {
    // ...Write message to file...
    var shouldWeContinue = ...  
    return shouldWeContinue;
  }

  public void DoSomething(Func<string, bool> errorHandler)
  {
    // In here, we don't care what handler we got passed!
    ...
    if (...error...)
    {
      if (!errorHandler("Some error occurred!"))
      {
        // The handler decided we can't continue
        return;
      }
    }
  }
}

Объединение делегатов (многоадресные делегаты)

Сложение + и вычитание - операции могут быть использованы для объединения экземпляров делегата. Делегат содержит список назначенных делегатов.

using System;
using System.Reflection;
using System.Reflection.Emit;

namespace DelegatesExample {
    class MainClass {
        private delegate void MyDelegate(int a);

        private static void PrintInt(int a) {
            Console.WriteLine(a);
        }

        private static void PrintType<T>(T a) {
            Console.WriteLine(a.GetType());
        }

        public static void Main (string[] args)
        {
            MyDelegate d1 = PrintInt;
            MyDelegate d2 = PrintType;

            // Output:
            // 1
            d1(1);

            // Output:
            // System.Int32
            d2(1);

            MyDelegate d3 = d1 + d2;
            // Output:
            // 1
            // System.Int32
            d3(1);

            MyDelegate d4 = d3 - d2;
            // Output:
            // 1
            d4(1);

            // Output:
            // True
            Console.WriteLine(d1 == d4);
        }
    }
}

В этом примере d3 представляет собой комбинацию делегатов d1 и d2 , поэтому при вызове программа выводит как строки 1 и System.Int32 .


Объединение делегатов с непустыми возвращаемыми типами:

Если групповой делегат имеет nonvoid типа возвращаемого, вызывающий абонент получает возвращаемое значение из последнего метода , который будет вызвано. Предыдущие методы все еще вызываются, но их возвращаемые значения отбрасываются.

    class Program
    {
        public delegate int Transformer(int x);

        static void Main(string[] args)
        {
            Transformer t = Square;
            t += Cube;
            Console.WriteLine(t(2));  // O/P 8 
        }

        static int Square(int x) { return x * x; }

        static int Cube(int x) { return x*x*x; }
    }

t(2) вызовет первый Square а затем Cube . Возвращаемое значение Square отбрасывается и возвращается значение последнего метода, т.е. Cube сохраняется.

Деактивировать многоадресную рассылку

Когда-либо хотелось вызвать делегата многоадресной рассылки, но вы хотите, чтобы весь список вызовов был вызван, даже если исключение происходит в любом из цепочки. Тогда вам повезло, я создал метод расширения, который делает именно это, бросая AggregateException только после завершения полного списка:

public static class DelegateExtensions
{
    public static void SafeInvoke(this Delegate del,params object[] args)
    {
        var exceptions = new List<Exception>();

        foreach (var handler in del.GetInvocationList())
        {
            try
            {
                handler.Method.Invoke(handler.Target, args);
            }
            catch (Exception ex)
            {
                exceptions.Add(ex);
            }
        }

        if(exceptions.Any())
        {
            throw new AggregateException(exceptions);
        }
    }
}

public class Test
{
    public delegate void SampleDelegate();

    public void Run()
    {
        SampleDelegate delegateInstance = this.Target2;
        delegateInstance += this.Target1;

        try
        {
            delegateInstance.SafeInvoke();
        } 
        catch(AggregateException ex)
        {
            // Do any exception handling here
        }
    }

    private void Target1()
    {
        Console.WriteLine("Target 1 executed");
    }

    private void Target2()
    {
        Console.WriteLine("Target 2 executed");
        throw new Exception();
    }
}

Эти результаты:

Target 2 executed
Target 1 executed

Вызов напрямую, без SaveInvoke , выполнит только Target 2.

Закрытие внутри делегата

Закрытие - это встроенные анонимные методы, которые имеют возможность использовать переменные метода Parent и другие анонимные методы, которые определены в области родителя.

По сути, закрытие представляет собой блок кода, который может быть выполнен в более позднее время, но который поддерживает среду, в которой он был сначала создан, т. Е. Он все еще может использовать локальные переменные и т. Д. Метода, который его создал, даже после этого метод завершил выполнение. - Джон Скит

delegate int testDel();
static void Main(string[] args)
{
    int foo = 4;
    testDel myClosure = delegate()
    {
        return foo;
    };
    int bar = myClosure();

}

Пример, взятый из Closures в .NET .

Инкапсулирующие преобразования в функциях

public class MyObject{
    public DateTime? TestDate { get; set; }

    public Func<MyObject, bool> DateIsValid = myObject => myObject.TestDate.HasValue && myObject.TestDate > DateTime.Now;

    public void DoSomething(){
        //We can do this:
        if(this.TestDate.HasValue && this.TestDate > DateTime.Now){
            CallAnotherMethod();
        }

        //or this:
        if(DateIsValid(this)){
            CallAnotherMethod();
        }
    }
}

В духе чистого кодирования инкапсуляция проверок и преобразований, подобных описанной выше как Func, может упростить чтение и понимание кода. В то время как приведенный выше пример очень прост, что, если было несколько свойств DateTime, каждый из которых имеет свои собственные правила проверки достоверности, и мы хотели проверить разные комбинации? Простые однострочные Funcs, у которых установлена ​​логика возврата, могут быть как читаемыми, так и уменьшать кажущуюся сложность вашего кода. Рассмотрим приведенные ниже вызовы Func и представьте, сколько еще кода будет загромождать метод:

public void CheckForIntegrity(){
    if(ShipDateIsValid(this) && TestResultsHaveBeenIssued(this) && !TestResultsFail(this)){
        SendPassingTestNotification();
    }
}


Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow