Главная | Обратная связь | Поможем написать вашу работу!
МегаЛекции

Events. Lambda Expressions




Events

Code Snippet: A simple delegate event which fires when a Unity click event is detected:
public delegate void ButtonEvent ();

 public event ButtonEvent OnClick = delegate { };

 

public void OnPointerClick (PointerEventData evd) {

   OnClick?. Invoke();

}

Class which receives the events:
public ButtonGeneric hireButton;

 

 private void OnEnable() {

    hireButton. OnClick += HireButton_OnClick;

 }

 

 private void OnDisable() {

    hireButton. OnClick -= HireButton_OnClick;

 }

 

 private void HireButton_OnClick() {

    //Code Goes Here!;

 }

(PREREQUISITE: Delegates, Generics, Inheritance, Class Creation):

Delegates: Events hold delegate instances (although Events are NOT delegate instances themselves! )

Generics: Events can be constructed to use Generics as in the case of EventHander< T>

Inheritance: Traditionally, Events are classes which inherit from EventArgs

Class Creation: Creating an Event which inherits from EventArgs also involves the use of Constructors to initialize the data in the Event.

Reference:

C# Events - Creating and Consuming Events in Your Application: The basics of Event creation and usage. All that you will “practically” need to know
https: //www. youtube. com/watch? v=-1cftB9q1kQ

Delegates and Events: Advanced explanation of Delegates and Event declaration. Most useful for understanding the difference between the two.
https: //csharpindepth. com/articles/Events

Microsoft Documentation:
https: //docs. microsoft. com/en-us/dotnet/standard/events/

Purpose; Notifications for when to execute code: An event represents a user defined trigger point. Through the use of multicast delegates, any number of methods can be fired to execute code before returning flow control to the point where the event fired.

Implementation:

Event Handlers: An Event Handler holds reference to any number of delegate instances which are executed when the event is invoked, and is declared with the “event” keyword:
public event EventHandler MyEventName;

Field-like event definition: The above represents the short-hand definition of an Event Handler, but this is the most common / modern way of defining events. The “long-hand” definition will be covered in the section “Event Handler Add / Remove Methods

EventHandler is a predefined delegate that specifically represents an event handler method for an event that does not generate data. Anything that would take the place of the predefined “EventHandler” would still be called an “Event Handler”.

MyEventName represents the identifier for the event which will be used to invoke it and which Listeners will use to subscribe and unsubscribe from

https: //docs. microsoft. com/en-us/dotnet/api/system. eventhandler? view=netframework-4. 8

Subscribe Listeners to Events: Before an Event can be fired, at least one delegate instance (which can be thought of as a listener) must be “subscribed” to the event handler. Delegate instances can be “added” to an Event Handler through the “+=” operator:
       classRef. MyEventName += ClassRefName_MyEventName;

classRef represents a reference to the class in which the event handler was defined (and where the event will be invoked from)

MyEventName represents the identifier for the event itself

ClassRefName_MyEventName represents the conventional way naming listener methods. By convention, they begin with the name of the class in which the method is defined, followed by an underscore, then the actual name of the event. This way, the Method’s name is a perfect reminder of where the event is being fired from, and what the Event name itself is.

The Listener Method: The listener method itself is a delegate instance which must conform to the method signature of the Event to which it is subscribing. It is defined in the class which is subscribing to the event:
private void ClassRefName_MyEventName(object sender, EventArgs e)

object sender is the standard argument name which contains a reference to the instance of the class which fired the event. As shown in the invocation step below, the calling instance passes a reference to itself using the this keyword.

EventArgs e is the standard argument name which contains the event data passed along with the invocation of the event.

Standard for the EventHandler delegate: The above method signature represents the method signature (return type and parameters) for an event created using the EventHandler predefined delegate. Other events which use EventHandler< T> or a custom EventHandler class will naturally have different parameters.

Invoking the Event: An Event is raised when the Event Handler is invoked. Just like when you invoke a delegate instance, you will also want to use the “? ” operator to ensure that there in fact exist some subscribers to the event before trying to invoke them:
MyEventName?. Invoke(this, EventArgs. Empty);

Passing This: The first parameter of the standard invocation of an event is of type object and is used to pass along a reference to the object which invoked the event.

No data to pass; EventArgs. Empty: The second parameter is of type EventArgs and is used to pass along data to the listeners. If no there is no meaningful data to pass along with the invocation, this parameters should be set to EventArgs. Empty.

Data to pass: If the event “generates data” (the event does have meaningful data to pass along to its listeners) it is passed as the second parameter through a Type which inherits from EventArgs.

I say when, anyone can listen; Only the Class in which the Event Handler is defined can raise (invoke) the Event, but any number of classes can subscribe to the handler.

Unsubscribe: Delegate instances are “unsubscribed” from an Event Handler through the “-=” operator.
classRef. MyEventName -= ClassRefName_MyEventName;

Housekeeping: Before destroying an instance of a Class which has subscribed itself to an Event, you must unsubscribe the Class methods from the Event Handler. If this is not done explicitly, there is a chance that the class is not garbage-collected and remains open in memory, eventually leading to a memory leak.

Note; Don’t subscribe anonymous functions: If you subscribe an anonymous functions function to an Event, you will have no practical means of unsubscribing from it, resulting in a memory leak.

Usage in Unity: The OnEnable method in Unity is a great opportunity to perform subscriptions, similarly, the OnDisable method should be used for Unsubscribing.

How are Events different from Multicast Delegates?

○ Events are similar to multicast delegates, but vary in the following ways:

Delegates are an input: Traditional delegate usage involves passing a delegate instance to be invoked as an input.

Implement the “Strategy Pattern”: When code needs to operate differently based on context, you can supply one of any number of delegate types to be invoked based on some condition. For example; in Paper Mario, each attack in the game could be a unique delegate type, then, based on the player’s choice of which attack they wish to perform, one of these delegate types is stored in a delegate instance and invoked as the player performs their attack.

Events are notifications: After an event handler is created, the class which created it “doesn’t care if anyone listens to it”. Other classes may or may not perform any code when they are notified.

Events can be used in interfaces: Like Unity’s mouse-pointer Interfaces which fire events when the mouse enters / exits / clicks

Only the declaring class can invoke the Event: Invocation access to the multicast delegate is limited to the declaring class. The behaviour is as though the delegate were private for invocation. For the purposes of assignment, access is as specified by an explicit access modifier (such as a public event).

Formalized Subscription: Events have actual “Add” and “Remove” methods which are rarely written out because they are implicit in the newer short-hand definition of Events. Still, these formal Add and Remove methods allow for “encapsulation without allowing arbitrary access to the list of event handlers. ”

Look at it in order: Event handlers are invoked in the order in which they subscribed to the original event. If an event handler were to modify the data being passed to it, future event handlers in the chain would be dealing with the modified data.

Inheriting from EventArgs:

Pass a Class: The traditional way of handling events is to create a Class which inherits from EventArgs and pass an instance of that class to your invocation:
Public class InheritingEventArgs: EventArgs { }

InheritingEventArgs represents a custom class which is inheriting from EventArgs at the top of its class definition.

Generate Custom Data: Creating a class which inherits from EventArgs allows for custom data to be generated by the event (passed along to listeners), as defined by the class.

Generate Generic data: It is also possible to use the generic EventHandler< T> delegate class if you do not wish to define your own. This is further explained in the section “Generic Event Handlers

Create a Constructor: The class you create which inherits from EventArgs should have properties which are private set, public get accessibility. These properties should be set by the Constructor.

Why we do this: Any Event handler which handles an event has the ability to modify the event data as it touches the EventArg passed to it

Intentional Manipulation: Some properties of the Event can be left public if they are intended to be modified by the Event Handlers.

Creating an instance of the class to pass: The class which inherits from EventArgs can be passed in one of two ways;

In-Line: On the line where the event is invoked, an instance of the EventArgs class can be declared as the argument to be passed to the invocation:
MyEventName?. Invoke(this, new InheritingEventArgs(instanceParameters));

instanceParameters refers to parameters passed to the newly created instance of InheritingEventArgs

Don’t expect any modification of data: The in-line declaration omits the need to create a variable which can be used to reference the data stored in the instance of InheritingEventArgs. This is fine as long as you don’t need access to this data on subsequent lines of code.

Creating a reference: If InheritingEventArgs contains data which you wish to reference, especially in the case where it is modified by any of the listeners, then you can create an instance of the EventArgs class before passing a reference to this class as the argument to Invoke:
InheritingEventArgs ieArgs = new InheritingEventArgs(instanceParameters);
MyEventName?. Invoke(this, ieArgs);
//Data in “ieArgs” is now accessible for use.

Paper Mario Example: In Paper Mario, you land an attack on the enemy. An event is fired to compute the damage dealt. Every attack-increasing badge the player has equipped has a listener on it to modify the base damage dealt by the attack. We will create a class which inherits from EventArgs to store a tally of the damage as it is passed around to the various listeners:

Public class DamageEventArgs: EventArgs {
public int talliedDamage {get; set; }
public Direction damageVector {get; private set; }
public DamageEventArgs(int _baseDamage, Direction _damageVector) {
      talliedDamage = _baseDamage;
      damageVector = _damageVector;
}

talliedDamage represents a property which will be set to some base amount but then increased by any number of event handlers.

damageVector represents a property which should be set as the event fires, but not manipulated by any event handler. The badges equipped to Mario could potentially increase the amount of damage they deal based on Mario’s vector of attack. For example, badges which increase Mario’s jump attack would only boost his damage if the “Direction” were “Down”.

Passing DamageEventArgs: We can now create an instance of the DamageEventArgs class and pass it as our second parameter when the event is invoked:

Int baseDamage = 1;
DamageEventArgs damageArg = new DamageEventArgs(baseDamage);
DamageEvent?. Invoke(this, damageArg);
//Events fire in damage-boosting-badges
enemy. dealDamage(damageArg. talliedDamage);

DamageEvent represents an Event Handler which is raised when an attack is executed.

baseDamage is fed in as the initial damage amount representing the fixed power of the attack

damageArg is created as an instance of the DamageEventArgs class. This is stored in a variable so that it can be later referenced as an argument by the dealDamage Method called on the targeted enemy

dealDamage represents a method which exists on the enemy that takes in an integer amount of damage. We pass it the tallied damage stored within damageArg.

Generic Event Handlers:

Declaration: Rather than creating your own class to handle an event (which inherits from EventArgs) you can use a generic Event Handler:
public event EventHandler< Type> EventHandlerIdentifier;

Type refers to whatever class the Event handler is using

EventHandlerIdentifier is simply the name for the Event, usually given a meaningful name which includes the word “event”. For example; “MenuSelectionMade”, “PlayerDeath”, “DamageEvent”

Invoking a Generic Event: Whichever Type was supplied in the definition of EventHandler< T> now becomes the type supplied as the second argument when the Event is invoked:
EventHandlerIdentifier?. Invoke(this, Type);

Example: The following represents an EventHandler declared with type string, and the invocation for such an event:
public event EventHandler< string> PassAStringEvent;

PassAStringEvent?. Invoke(this, “Passing a String”);

Event Handler Add / Remove Methods:

○ Event Handlers can also be declared with their own “Add / Remove” methods. The following “long-form” event declaration...

private EventHandler _myEvent;

public event EventHandler MyEvent

{

add

{

   lock (this)

   {

       _myEvent += value;

   }

}

remove

{

   lock (this)

   {

       _myEvent -= value;

   }

}       

}

○ ... Is equivalent to declaring a “field-like event” through the following “short-hand”:

public event EventHandler MyEvent;

The usage of “lock” statements guarantees “thread safety” but this may not be necessary if you have complete control of your application and are only running on a single “thread”. If this is the case, the Lock statements are not necessary:

private EventHandler _myEvent;

public event EventHandler MyEvent

{

add

{

   _myEvent += value;

}

remove

{

   _myEvent -= value;

}       

}

Execute code in the Add / Remove methods: Much like the “get / set” methods in Properties, Add / Remove methods are an opportunity to execute code upon the addition of new delegate instances to the Event Handler. This could be used for debugging purposes.

When are Add / Remove methods called: In C#, the Add method of an event is called when a delegate instance is subscribed to an Event (+=), and subsequently the Remove method is called when the delegate instance is unsubscribed (-=).

How to raise base class events in derived classes:

Reference: How to raise base class events in derived classes - C# Programming Guide

○ You can create a protected invoking method in the base class that wraps the event.

■ By calling or overriding this invoking method, derived classes can invoke the event indirectly

○ Do not declare virtual events in a base class and override them in a derived class.

■ The C# compiler does not handle these correctly and it is unpredictable whether a subscriber to the derived event will actually be subscribing to the base class event.

Lambda Expressions

PREREQUISITE (Delegates, Events):

Delegates: Lambdas ARE delegates, specifically, they are “anonymous delegates” (delegates without a name)

○ Lambda expressions are an especially good candidate for use in places where you use Func< T>

Events: Just as delegate instances are subscribed to Events, Lambda expressions (which are just anonymous delegates) can also be subscribed to events.

Lambda Expressions: An anonymous function

Reference:

Jeremy Clark: Learn to Love Lambdas: https: //www. youtube. com/watch? v=bMCoJ055F0c

JeremyBytes - Lambdas & LINQ in C# - Bonus: Captured Variables & for Loops: Dealing with capturing the value value of an index variable:
https: //www. youtube. com/watch? v=T1onS_dCheY

Microsoft Documentation:
https: //docs. microsoft. com/en-us/dotnet/csharp/programming-guide/statements-expressions-operators/lambda-expressions

Common Uses for Lambda Expressions:

○ LINQ

○ Built-in Delegate Types; Func< T> and Action< T>

○ Callbacks and Event Handlers

From delegate to Lambda: The following code represents how a delegate can be rewritten as a Lambda expression:
https: //youtu. be/0nd-tcQcslc? t=530

Named Delegate phase: The delegate is fully defined with a name:
Public returnType ClassBMethodName(ClassAType inputDelegate) {
return returnType;

}

ClassAType represents the original class in which a delegatedable method was defined

ClassBMethodName represents a class different from class A in which this new delegate has been defined.

Anonymous delegate phase: A delegate is defined with the “delegate” keyword as an anonymous delegate in-line:
delegateInstance = delegate(delegateParameters) {
return returnType;

};

delegateInstance stores a reference to the delegate

■ For more information about this type of definition, see “Delegates > Anonymous Delegates > In-line definition

Lambda phase: An anonymous delegate can easily be rewritten with the addition of a Lambda expression and the removal of the delegate keyword:
delegateInstance = delegate(delegateParameters) => {
return returnType;

};

Ongoing Evolution: The delegate is now defined by a Lambda expression, but it can be further compressed due to the Syntactic Variation allowed in the use of Lambda expressions. Following a more clear explanation of the “Anatomy” of a Lambda expression, the “Syntactic Variation” section will continue to show how much further our original example can be reduced.

Omitting Return: The final phase of replacing a named delegate with an “Expression Lambda” is the ability to omit the use of the return keyword:
delegateInstance = p => p. MethodCall();

■ p is a single-letter lambda parameter. Its type is implicitly defined through what p is allowed to be in this situation.

MethodCall refers to some operation performed on the object p. It should be noted that we are not storing the result of the method call in the delegateInstance; rather, when delegateInstance is itself passed as a parameter to a method, it will hold a reference to the operation defined by the lambda expression.

Anatomy:
(Person person) => { person. FirstName == “John”}

Parameter (s) on the left-hand side

Comma delimited: (Person p, int i)

○ => “Goes to” operator (aka Lambda operator )

■ Separates the input variables on the left side from the lambda body on the right side
https: //docs. microsoft. com/en-us/dotnet/csharp/language-reference/operators/lambda-operator

Expression or Statements:

Expression Lambda: Returns a value, but omits the “return” keyword

Statement Lambda: Does not return a value, but does some type of work through one or more statements.

Miniaturized Parameters: It is common to use single-letter parameters in Lambda expressions;
(Person p) => { p. FirstName == “John”}

Syntactic Variation:

○ A common reason why lambda expressions can be so difficult to read; the compiler is very generous when it comes to defining lambda expressions

Optional Parameter Types (Parameter Type Inference): Where the compiler can infer the type of a parameter, it does not need to be specified:
(p) => { p. FirstName == “John”}

Optional Parentheses for a Single Parameter: When you are only using a single parameter, you can omit the parenthesis around it:
p => { p. FirstName == “John”}

Optional Braces for a Single Statement: Just like an if statement, you can omit the use of {braces} for single-statements:
p => p. FirstName == “John”

Zero Parameter Expression: When your Lambda expression takes no parameters, you can simply use an empty set of parenthesis:
() => { Console. Write(“John”); }

Func< T> and Action< T> with Lambdas:

○ Func< T> represents an excellent opportunity to use Lambda expressions to create an anonymous delegate. Here is an example:
Func< Person, string> personFormatter =
p => p. LastName. ToUpper();

Func< Person, string> represents an anonymous delegate which takes in a type of Person and returns a type string.

personFormatter stores a reference to the anonymous delegate we are creating “p => p. LastName. ToUpper(); ” which operates on a variable of type Person (p) which returns an uppercased version of that Person’s lastName.

Callbacks and Event Handlers:

Here is an example:
Var proxy = new PersonServiceClient();
proxy. GetPeopleCompleted +=
(s, a) => PersonListBox. ItemsSource = a. Result;
proxy. GetPeopleAsync();

proxy represents a reference to a class which holds the Event “GetPeopleCompleted”

(s, a) => PersonListBox. ItemsSource = a. Result; represents an inlined anonymous delegate created with a Lambda expression.

Captured Variables (AKA Enclosure):

○ A feature of Lambda expressions (and by extension, anonymous delegates)

Scope defined upon assignment: Any variable which was in scope at the time the method body was assigned to an event Handler is usable within the method body.
private void MyEventMethod(object sender, EventArgs e) {
int myNumber = 5;
myEventHandler. OnEventFired += (LambdaParameters) => { LambdaBody };
}

myNumber represents a variable which is captured on the assignment of the Lambda expression to the firing of “OnEventFired”

Without Captured Variables: For the sake of contrast, without the ability to capture variables, at the moment the time the closing brace of MyEventMethod is reached, myNumber would become eligible for Garbage Collection and therefore be unusable in the body of the Lambda expression.

Arguments too: If the lambda expression is defined within the scope of another method, the arguments passed to that method are also considered to be in scope, and therefore usable by the lambda expression.

■ In the above example, the Lambda expression could make use of the sender object, or the EventArgs.

The reference is captured, not the value: The value in the variable is not preserved from the moment it is captured. The lambda expression simply keeps a reference to the variable to prevent it from being garbage collected.

What this means: If anything modifies the captured variable, the lambda expression will use the modified value:

private void MyEventMethod(object sender, EventArgs e) {
int myNumber = 5;
myEventHandler. OnEventFired += (LambdaParameters) => { LambdaBody };
int myNumber = -1;
}

■ Even though myNumber is modified to hold a value of “-1” after the assignment of the lambda expression to the Event Handler, its value will be read as “-1” within the body of the Lambda expression, and in fact can be further modified within the expression body.

Watch out for capturing indexer values: There are issues associated with accidentally or intentionally trying to capture the indexer value of a for loop.

i = Length: Since the for loop continues to modify the value of its index variable, by the time the Lambda expression gets to use it, the value of the indexer is likely to be at its maximum length.

Define a local-scope variable to fix this: By declaring a variable which holds the value of “i” that is local in scope to the body of the For Loop, and using this variable in place of any place where we wish to use “i” this ensures that each iteration of the for loop creates a unique instance of the captured variable.

■ For more information and an example of how to implement this, view the following video:
https: //www. youtube. com/watch? v=T1onS_dCheY

Lambda Operator: See section “Operators, => Operator”

Поделиться:





Воспользуйтесь поиском по сайту:



©2015 - 2024 megalektsii.ru Все авторские права принадлежат авторам лекционных материалов. Обратная связь с нами...