Buscar..


Observaciones

Resumen

Un tipo de delegado es un tipo que representa una firma de método particular. Una instancia de este tipo se refiere a un método particular con una firma coincidente. Los parámetros de los métodos pueden tener tipos de delegado, por lo que este único método puede pasar una referencia a otro método, que luego puede invocarse

Tipos de delegados incorporados: Action<...> , Predicate<T> y Func<...,TResult>

El espacio de nombres del System contiene delegados de Action<...> , Predicate<T> y Func<...,TResult> , donde "..." representa entre 0 y 16 parámetros de tipo genérico (para 0 parámetros, Action no. genérico).

Func representa métodos con un tipo de retorno que coincide con TResult , y Action representa métodos sin un valor de retorno (vacío). En ambos casos, los parámetros de tipo genérico adicionales coinciden, en orden, con los parámetros del método.

Predicate representa el método con el tipo de retorno booleano, T es el parámetro de entrada.

Tipos de delegados personalizados

Los tipos de delegado con nombre se pueden declarar utilizando la palabra clave delegate .

Invocando delegados

Los delegados pueden invocarse utilizando la misma sintaxis que los métodos: el nombre de la instancia del delegado, seguido de paréntesis que contienen cualquier parámetro.

Asignación a delegados

Los delegados pueden ser asignados de las siguientes maneras:

  • Asignando un método nombrado
  • Asignar un método anónimo utilizando un lambda
  • Asignación de un método nombrado utilizando la palabra clave delegate .

Combinando delegados

Se pueden asignar varios objetos delegados a una instancia de delegado utilizando el operador + . El operador - se puede usar para eliminar un componente delegado de otro delegado.

Referencias subyacentes de los delegados de métodos nombrados

Al asignar métodos nombrados a los delegados, se referirán al mismo objeto subyacente si:

  • Son el mismo método de instancia, en la misma instancia de una clase

  • Son el mismo método estático en una clase.

    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
    

Declarar un tipo de delegado

La siguiente sintaxis crea un tipo de delegate con el nombre NumberInOutDelegate , que representa un método que toma un int y devuelve un int .

public delegate int NumberInOutDelegate(int input);

Esto se puede utilizar de la siguiente manera:

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

El example instancia delegado se ejecuta de la misma manera como el Square método. Una instancia de delegado actúa literalmente como un delegado para la persona que llama: la persona que llama invoca al delegado, y luego el delegado llama al método de destino. Esta indirección desacopla al llamante del método de destino.


Puede declarar un tipo de delegado genérico y, en ese caso, puede especificar que el tipo sea covariante ( out ) o contravariante ( in ) en algunos de los argumentos de tipo. Por ejemplo:

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

Al igual que otros tipos genéricos, los tipos de delegados genéricos pueden tener restricciones, como where TFrom : struct, IConvertible where TTo : new() .

Evite la covarianza y la contravarianza para los tipos de delegados que están destinados a ser utilizados para delegados de multidifusión, como los tipos de controlador de eventos. Esto se debe a que la concatenación ( + ) puede fallar si el tipo de tiempo de ejecución es diferente del tipo de tiempo de compilación debido a la varianza. Por ejemplo, evita:

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

En su lugar, utilice un tipo genérico invariante:

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

También se admiten delegados donde algunos parámetros se modifican por ref o out , como en:

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

(uso de la muestra TryParser<decimal> example = decimal.TryParse; ), o delegados donde el último parámetro tiene el params modificador. Los tipos de delegado pueden tener parámetros opcionales (suministrar valores predeterminados). Los tipos de delegados pueden usar tipos de punteros como int* o char* en sus firmas o tipos de devolución (use palabras clave unsafe ). Un tipo de delegado y sus parámetros pueden llevar atributos personalizados.

El funcional Acción y Predicado tipos de delegado

El espacio de nombres del sistema contiene tipos de delegado Func<..., TResult> con entre 0 y 15 parámetros genéricos, devolviendo el tipo 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);
}

El espacio de nombres del sistema también contiene los tipos de delegados de la Action<...> con un número diferente de parámetros genéricos (de 0 a 16). Es similar a Func<T1, .., Tn> , pero siempre devuelve 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> también es una forma de Func pero siempre devolverá bool . Un predicado es una forma de especificar un criterio personalizado. Dependiendo del valor de la entrada y la lógica definida dentro del predicado, devolverá true o false . Predicate<T> por lo tanto, se comporta de la misma manera que Func<T, bool> y ambos pueden inicializarse y usarse de la misma manera.

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

La elección de utilizar Predicate<T> o Func<T, bool> es realmente una cuestión de opinión. Predicate<T> es posiblemente más expresivo de la intención del autor, mientras que Func<T, bool> es probable que sea familiar para una mayor proporción de desarrolladores de C #.

Además de eso, hay algunos casos en los que solo una de las opciones está disponible, especialmente al interactuar con otra API. Por ejemplo, List<T> y Array<T> generalmente toman Predicate<T> para sus métodos, mientras que la mayoría de las extensiones LINQ solo aceptan Func<T, bool> .

Asignar un método nombrado a un delegado

Los métodos nombrados se pueden asignar a delegados con firmas coincidentes:

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


Func<int,int> addOne = Example.AddOne

Example.AddOne toma un int y devuelve un int , su firma coincide con el delegado Func<int,int> . Example.AddOne puede asignarse directamente a addOne porque tienen firmas coincidentes.

Igualdad de delegados

Al llamar a .Equals() en un delegado se compara por igualdad de referencia:

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

Estas reglas también se aplican al hacer += o -= en un delegado de multidifusión, por ejemplo, al suscribirse y cancelar la suscripción a eventos.

Asignar a un delegado por lambda

Lambdas se puede usar para crear métodos anónimos para asignar a un delegado:

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

Tenga en cuenta que la declaración explícita de tipo se requiere al crear una variable de esta manera:

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

Pasando delegados como parámetros

Los delegados se pueden utilizar como punteros de función escritos:

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

Combinar delegados (delegados de multidifusión)

Operaciones de suma + y resta - se pueden utilizar para combinar instancias delegadas. El delegado contiene una lista de los delegados asignados.

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

En este ejemplo, d3 es una combinación de delegados d1 y d2 , por lo tanto, cuando se llama, el programa produce las cadenas 1 y System.Int32 .


Combinando delegados con tipos de retorno no nulos :

Si un delegado de multidifusión tiene un tipo de retorno no nonvoid , la persona que llama recibe el valor de retorno del último método a invocar. Los métodos anteriores todavía se llaman, pero sus valores de retorno se descartan.

    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) llamará primero a Square y luego a Cube . El valor de retorno de Cuadrado se descarta y el valor de retorno del último método, es decir, el Cube se retiene.

Seguro invocar delegado de multidifusión

Alguna vez quiso llamar a un delegado de multidifusión, pero desea que se llame a toda la lista de llamadas, incluso si se produce una excepción en alguna de las cadenas. Entonces estás de suerte, he creado un método de extensión que hace precisamente eso, lanzando una AggregateException solo después de que se completa la ejecución de la lista completa:

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

Esto produce:

Target 2 executed
Target 1 executed

Invocar directamente, sin SaveInvoke , solo ejecutaría Target 2.

Cierre dentro de un delegado

Los cierres son métodos anónimos en línea que tienen la capacidad de usar variables de métodos Parent y otros métodos anónimos que se definen en el alcance de los padres.

En esencia, un cierre es un bloque de código que puede ejecutarse en un momento posterior, pero que mantiene el entorno en el que se creó por primera vez, es decir, aún puede usar las variables locales, etc., del método que lo creó, incluso después de eso. El método ha terminado de ejecutarse. - Jon Skeet

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

}

Ejemplo tomado de Closures en .NET .

Encapsulando transformaciones en funciones.

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

En el espíritu de la codificación limpia, las comprobaciones y transformaciones encapsuladas como la de arriba como Func pueden hacer que su código sea más fácil de leer y entender. Si bien el ejemplo anterior es muy simple, ¿qué pasaría si hubiera varias propiedades de DateTime con sus propias reglas de validación diferentes y quisiéramos verificar diferentes combinaciones? Las funciones simples, de una línea, cada una de las cuales ha establecido una lógica de retorno, pueden ser legibles y reducir la complejidad aparente de su código. Considere las siguientes llamadas a funciones e imagínese cuánto más código estaría saturando el método:

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


Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow