Zdaję sobie sprawę, że to pytanie ma ponad 10 lat, ale wydaje mi się, że nie tylko nie udzielono odpowiedzi na najbardziej oczywistą odpowiedź, ale może nie do końca wynika z pytania dobre zrozumienie tego, co dzieje się pod kołdrą. Ponadto istnieją inne pytania dotyczące późnego wiązania i tego, co to oznacza w odniesieniu do delegatów i lambd (więcej o tym później).
Najpierw zajmij się słoniem / gorylem o wadze 800 funtów w pokoju, kiedy wybrać eventvs Action<T>/ Func<T>:
- Użyj lambdy, aby wykonać jedną instrukcję lub metodę. Użyj,
eventgdy chcesz mieć więcej modelu pub / sub z wieloma instrukcjami / lambdami / funkcjami, które będą wykonywane (jest to główna
różnica od razu).
- Użyj lambdy, gdy chcesz skompilować instrukcje / funkcje do drzew wyrażeń. Użyj delegatów / zdarzeń, jeśli chcesz uczestniczyć w bardziej tradycyjnym późnym wiązaniu, takim jak używane w odbiciu i międzyoperacyjności modelu COM.
Jako przykład zdarzenia pozwala połączyć prosty i „standardowy” zestaw zdarzeń za pomocą małej aplikacji konsoli w następujący sposób:
public delegate void FireEvent(int num);
public delegate void FireNiceEvent(object sender, SomeStandardArgs args);
public class SomeStandardArgs : EventArgs
{
public SomeStandardArgs(string id)
{
ID = id;
}
public string ID { get; set; }
}
class Program
{
public static event FireEvent OnFireEvent;
public static event FireNiceEvent OnFireNiceEvent;
static void Main(string[] args)
{
OnFireEvent += SomeSimpleEvent1;
OnFireEvent += SomeSimpleEvent2;
OnFireNiceEvent += SomeStandardEvent1;
OnFireNiceEvent += SomeStandardEvent2;
Console.WriteLine("Firing events.....");
OnFireEvent?.Invoke(3);
OnFireNiceEvent?.Invoke(null, new SomeStandardArgs("Fred"));
//Console.WriteLine($"{HeightSensorTypes.Keyence_IL030}:{(int)HeightSensorTypes.Keyence_IL030}");
Console.ReadLine();
}
private static void SomeSimpleEvent1(int num)
{
Console.WriteLine($"{nameof(SomeSimpleEvent1)}:{num}");
}
private static void SomeSimpleEvent2(int num)
{
Console.WriteLine($"{nameof(SomeSimpleEvent2)}:{num}");
}
private static void SomeStandardEvent1(object sender, SomeStandardArgs args)
{
Console.WriteLine($"{nameof(SomeStandardEvent1)}:{args.ID}");
}
private static void SomeStandardEvent2(object sender, SomeStandardArgs args)
{
Console.WriteLine($"{nameof(SomeStandardEvent2)}:{args.ID}");
}
}
Wynik będzie wyglądał następująco:

Jeśli zrobiłeś to samo z Action<int> lub Action<object, SomeStandardArgs>, zobaczyłbyś tylko SomeSimpleEvent2i SomeStandardEvent2.
Więc co się dzieje w środku event?
Jeśli się rozszerzymy FireNiceEvent , kompilator faktycznie generuje następujące elementy (pominąłem niektóre szczegóły dotyczące synchronizacji wątków, które nie są istotne w tej dyskusji):
private EventHandler<SomeStandardArgs> _OnFireNiceEvent;
public void add_OnFireNiceEvent(EventHandler<SomeStandardArgs> handler)
{
Delegate.Combine(_OnFireNiceEvent, handler);
}
public void remove_OnFireNiceEvent(EventHandler<SomeStandardArgs> handler)
{
Delegate.Remove(_OnFireNiceEvent, handler);
}
public event EventHandler<SomeStandardArgs> OnFireNiceEvent
{
add
{
add_OnFireNiceEvent(value)
}
remove
{
remove_OnFireNiceEvent(value)
}
}
Kompilator generuje prywatną zmienną delegata, która nie jest widoczna dla przestrzeni nazw klasy, w której jest generowana. Ten delegat jest używany do zarządzania subskrypcjami i udziału w późnym wiązaniu, a interfejs publiczny jest dobrze znany+= i-= wszyscy operatorzy, których wszyscy znamy i kochamy :)
Możesz dostosować kod dla programów obsługi dodawania / usuwania, zmieniając zakres FireNiceEvent delegata na protected. Umożliwia to programistom dodawanie niestandardowych punktów zaczepienia do punktów zaczepienia, takich jak punkty zaczepienia logowania lub zabezpieczenia. To naprawdę tworzy bardzo potężne funkcje, które teraz pozwalają na dostosowaną dostępność do subskrypcji w oparciu o role użytkowników itp. Czy możesz to zrobić z lambdami? (Właściwie możesz przez niestandardowe kompilowanie drzew wyrażeń, ale to wykracza poza zakres tej odpowiedzi).
Aby odnieść się do kilku punktów z niektórych odpowiedzi tutaj:
Naprawdę nie ma różnicy w „kruchości” między zmianą listy argumentów w programie Action<T>a zmianą właściwości w klasie pochodnej EventArgs. Oba będą wymagały nie tylko zmiany kompilacji, ale także zmienią publiczny interfejs i będą wymagały wersjonowania. Bez różnicy.
W odniesieniu do tego, który jest standardem branżowym, zależy to od tego, gdzie jest on używany i dlaczego. Action<T>i taki jest często używany w IoC i DI, i eventjest często używany w routingu komunikatów, takim jak struktury typu GUI i MQ. Zauważ, że mówiłem często , nie zawsze .
Delegaci mają inne okresy istnienia niż lambdy. Trzeba też być świadomym złapania ... nie tylko z zamknięciem, ale także z pojęciem „patrz, co kot wciągnął”. Ma to wpływ na zużycie pamięci / czas życia, a także zarządzanie, czyli wycieki.
Jeszcze jedno, coś, o czym wspomniałem wcześniej ... pojęcie późnego wiązania. Często zobaczysz to podczas korzystania z frameworka takiego jak LINQ, dotyczącego tego, kiedy lambda staje się „aktywna”. To bardzo różni się od późnego wiązania delegata, które może się zdarzyć więcej niż jeden raz (tzn. Lambda jest zawsze, ale wiązanie występuje na żądanie tak często, jak jest to potrzebne), w przeciwieństwie do lambda, które po wystąpieniu jest wykonywane - magia zniknęła, a metoda (-y) / właściwość (-y) zawsze będą się wiązać. Coś, o czym warto pamiętać.