Generics in the .NET Framework
Generics let you tailor a method, class, structure, or interface to the precise data type it acts upon. For example, instead of using the Hashtable class, which allows keys and values to be of any type, you can use the Dictionary<TKey, TValue> generic class and specify the type allowed for the key and the type allowed for the value. Among the benefits of generics are increased code reusability and type safety.
Covariance and contravariance of generic type parameters enable you to use constructed generic types whose type arguments are more derived (covariance) or less derived (contravariance) than a target constructed type.
Generic methods can appear on generic or nongeneric types. It is important to note that a method is not generic just because it belongs to a generic type, or even because it has formal parameters whose types are the generic parameters of the enclosing type. A method is generic only if it has its own list of type parameters. In the following code, only method G is generic.
Another example for this; The Array.ForEach<T> method must be static and generic because Array is not a generic type; the only reason you can specify a type for Array.ForEach<T> to operate on is that the method has its own type parameter list. By contrast, the nongeneric List<T>.ForEach method belongs to the generic class List<T>, so it simply uses the type parameter of its class. The class is strongly typed, so the method can be an instance method.
Generics supplies Type safety. Errors might catch on compile time. This is important. Generic collection types generally perform better for storing and manipulating value types because there is no need to box the value types.
Covariance and contravariance are terms that refer to the ability to use a less derived (less specific) or more derived type (more specific) than originally specified. Generic type parameters support covariance and contravariance to provide greater flexibility in assigning and using generic types.
Invariance means that you can use only the type originally specified; so an invariant generic type parameter is neither covariant nor contravariant.
Events in the .NET Framework are based on the delegate model. The delegate model follows the observer design pattern, which enables a subscriber to register with, and receive notifications from, a provider. An event sender pushes a notification that an event has happened, and an event receiver receives that notification and defines a response to it.
Here is observer pattern is explained: https://sourcemaking.com/design_patterns/observer
The event sender doesn’t know which object or method will receive (handle) the events it raises. The event is typically a member of the event sender; for example, the Click event is a member of the Button class, and the PropertyChanged event is a member of the class that implements the INotifyPropertyChanged interface.
Delegates have many uses in the .NET Framework. In the context of events, a delegate is an intermediary (or pointer-like mechanism) between the event source and the code that handles the event. You associate a delegate with an event by including the delegate type in the event declaration.
Delegates are multicast, which means that they can hold references to more than one event-handling method.
For scenarios where the EventHandler and EventHandler<TEventArgs> delegates do not work, you can define a delegate. Scenarios that require you to define a delegate are very rare, such as when you must work with code that does not recognize generics.
The .NET Framework follows a naming pattern of ending all event data classes with EventArgs.
When you want to create a customized event data class, create a class that derives from EventArgs, and then provide any members needed to pass data that is related to the event.
Typically, you should use the same naming pattern as the .NET Framework and end your event data class name with EventArgs.
To respond to an event, you define an event handler method in the event receiver. This method must match the signature of the delegate for the event you are handling. In the event handler, you perform the actions that are required when the event is raised, such as collecting user input after the user clicks a button. To receive notifications when the event occurs, your event handler method must subscribe to the event.
If your class raises multiple events, the compiler generates one field per event delegate instance. If the number of events is large, the storage cost of one field per delegate may not be acceptable. For those situations, the .NET Framework provides event properties that you can use with another data structure of your choice to store event delegates.
The observer design pattern enables a subscriber to register with and receive notifications from a provider. It is suitable for any scenario that requires push-based notification. The pattern defines a provider (also known as a subject or an observable) and zero, one, or more observers.
In the .NET Framework, the observer design pattern is applied by implementing the generic System.IObservable<T> and System.IObserver<T> interfaces. The generic type parameter represents the type that provides notification information.
The observer must implement three methods, all of which are called by the provider:
1- IObserver<T>.OnNext, which supplies the observer with new or current information.
2- IObserver<T>.OnError, which informs the observer that an error has occurred.
3- IObserver<T>.OnCompleted, which indicates that the provider has finished sending notifications.
Typically, the provider uses a container object, such as a System.Collections.Generic.List<T> object, to hold references to the IObserver<T> implementations that have subscribed to notifications. Using a storage container for this purpose enables the provider to handle zero to an unlimited number of observers. The order in which observers receive notifications is not defined; the provider is free to use any method to determine the order.
You can find full example for observer pattern at: https://github.com/reyou/Sample-Observer-Design-Pattern
An observer can call Subscribe and Dispose methods at any time. In addition, because the provider/observer contract does not specify who is responsible for unsubscribing after the IObserver<T>.OnCompleted callback method, the provider and observer may both try to remove the same member from the list. Because of this possibility, both the Subscribe and Dispose methods should be thread-safe. Typically, this involves using a concurrent collection or a lock. Implementations that are not thread-safe should explicitly document that they are not.
The OnError method is intended as an informational message to observers, much like the IObserver<T>.OnNext method. However, the OnNext method is designed to provide an observer with current or updated data, whereas the OnError method is designed to indicate that the provider is unable to provide valid data.
The provider should follow these best practices when handling exceptions and calling the OnError method:
– The provider must handle its own exceptions if it has any specific requirements.
– The provider should not expect or require that observers handle exceptions in any particular way.
– The provider should call the OnError method when it handles an exception that compromises its ability to provide updates. Information on such exceptions can be passed to the observer. In other cases, there is no need to notify observers of an exception.
Once the provider calls the OnError or IObserver<T>.OnCompleted method, there should be no further notifications, and the provider can unsubscribe its observers.
The observer should follow these best practices when responding to an OnError method call from a provider:
– The observer should not throw exceptions from its interface implementations, such as OnNext or OnError. However, if the observer does throw exceptions, it should expect these exceptions to go unhandled.
– To preserve the call stack, an observer that wishes to throw an Exception object that was passed to its OnError method should wrap the exception before throwing it. A standard exception object should be used for this purpose.
Attempting to unregister in the IObservable<T>.Subscribe method may result in a null reference. Therefore, we recommend that you avoid this practice.
Although it is possible to attach an observer to multiple providers, the recommended pattern is to attach an IObserver<T> instance to only one IObservable<T> instance.