Saturday 20 December 2014

Dot Net Questions - Part 1

  1. How you can build Robust, Scalable and Fault Tolerant application?
  2. What is a smart Client?
  3. How to handle a situation where Service data contract is changing very frequently?
  4. How GC works.
  5. What if dispose function is not implemented then how you will release unmanaged resources?
  6. What if your application is loading lots of data at startup? Your application will be unresponsive until all the data is not loaded. What are the different ways to handle this situation?
  7. What is Covariance and Contravariance
  8. IEnumerable vs IQueryable
  9. How finalize method will impact GC?
  10. SQL - Lets say there are millions of records in a table, provide the options to optimize its performance.
  11. SQL - What are the different ways to optimize SQL server's performance?
 

Saturday 24 August 2013

IXmlSerializable – Control XML Serialization

Sometimes we are in hard need to control the xml serialization. One of these needs could be to reduce the serialized data, so that less data will travel over wire and hence we will get the improved data transfer speed.

To control it, we can use the IXmlSerializable interface. This interface contains three functions

  1. XmlSchema GetSchema()
  2. void ReadXml(XmlReader reader)
  3. void WriteXml(XmlWriter writer)

In these functions, we can control the serialization with the help of XMLReader and XMLWriter. IXmlSerializable interface is recognized by the both XmlSerializer and Data Contract Serializer; and hence it can be used used by WCF and ASMX Web Services.

Lets have a look into the code - 

Code

using System;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Schema;
using System.Xml.Serialization;

namespace XMLSerialization
{
internal class Program
{
private static void Main(string[] args)
{
Person[] p = new Person[3];
p[0] = new Person() { Name = "Sanjay", Age = 30 };
p[1] = new Person() { Name = "Sanjay Legha", Age = 0 };
p[2] = new Person() { Age = 30 };

//Person p = new Person() { Name = "Sanjay", Age = 50 };

string filePath = @"D:\Sanjay\TempCode\XMLSerialization\XMLSerialization\bin\Debug\Serialized.xml";
using (System.IO.FileStream fs = new System.IO.FileStream(filePath, System.IO.FileMode.Create))
{
System.Xml.Serialization.XmlSerializer x = new System.Xml.Serialization.XmlSerializer(p.GetType());
x.Serialize(fs, p);
}

using (System.IO.FileStream fs = new System.IO.FileStream(filePath, System.IO.FileMode.Open))
{
System.Xml.Serialization.XmlSerializer x = new System.Xml.Serialization.XmlSerializer(p.GetType());
object obj = x.Deserialize(fs);
}

Console.Write("Done");
Console.Read();
}
}

public class Person : IXmlSerializable
{
public Person()
{
this.Name = string.Empty;
this.Age = 0;
}

[XmlElement(IsNullable = false)]
public string Name { get; set; }

[XmlElement()]
public int Age { get; set; }

public XmlSchema GetSchema()
{
return null;
}

public void ReadXml(XmlReader reader)
{
if (reader.NodeType == XmlNodeType.Element)
{
XElement el = XNode.ReadFrom(reader) as XElement;

foreach (var item in el.Nodes())
{
if (item is XElement)
{
XElement node = item as XElement;
switch (node.Name.LocalName)
{
case "Name":
this.Name = node.Value;
break;

case "Age":
this.Age = Convert.ToInt32(node.Value);
break;
}
}
}
}
}

public void WriteXml(XmlWriter writer)
{
if (!string.IsNullOrWhiteSpace(Name))
writer.WriteElementString("Name", Name);

if (Age != 0)
writer.WriteElementString("Age", Age.ToString());
}
}
}

Output

<?xml version="1.0"?>
<ArrayOfPerson xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Person>
    <Name>Sanjay</Name>
    <Age>30</Age>
  </Person>
  <Person>
    <Name>Sanjay Legha</Name>
  </Person>
  <Person>
    <Age>30</Age>
  </Person>
</ArrayOfPerson>

Generic Serialize Deserialize - XmlSerializer

 

A generic Xml Serializer De-Serializer code snap

public class SerializeDeserialize
{
    public static void SerializeData<T>(string filePath, T data)
    {
        using (System.IO.FileStream fs = new System.IO.FileStream(filePath, System.IO.FileMode.CreateNew))
        {
            System.Xml.Serialization.XmlSerializer x = new System.Xml.Serialization.XmlSerializer(data.GetType());
            x.Serialize(fs, data);
        }
    }

    public static T DeSerializeData<T>(string filePath)
    {
        T data;
        using (System.IO.FileStream fs = new System.IO.FileStream(filePath, System.IO.FileMode.Open, System.IO.FileAccess.Read))
        {
            System.Xml.Serialization.XmlSerializer x = new System.Xml.Serialization.XmlSerializer(typeof(T));
            object obj = x.Deserialize(fs);
            data = (T)obj;
        }

        return data;
    }
}

Saturday 25 February 2012

Programmatically Compile And Use Code Using C#

 

Introduction

It is always better if you could create all your required classes in your project. But, sometimes it becomes very crucial if you could create a class for the specific situation at runtime or if you could change the existing one.

To overcome this situation, .Net Framework exposes some class that allow us to programmatically access the C# language compiler.

In this post I am demonstrating how to create an entity class and its collection class through a windows application, and passing the collection class object to the DataGridView as DataSource.

Note: I am assuming you have the basic knowledge of reflection.

How To

Setup the project and form

In this example I am using the basic windows forms application. You can follow the following steps to setup this application -

  • Create a Windows Forms Application. It will create a form with name Form1.
  • Add a button on Form1 and name it btnCompile.
  • Add a DataGridView. Here I am using its default name dataGridView1.
  • Double click on the btnCompile to generate the event handler.

 

Implement the compiler

After adding code in event handler, your code should look like -

private void btnCompile_Click(object sender, EventArgs e)
{
    // code string to compile
    string code =
@"class EntityCollection : System.Collections.Generic.List<Entity> {}

class Entity
{
    public string Name { get; set; }

    public int Age { get; set; }
}";

    // RETRIEVING COMPILER INTERFACE
    CSharpCodeProvider codeProvider = new CSharpCodeProvider();
    ICodeCompiler codeCompiler = codeProvider.CreateCompiler();


    // PREPARING COMPILER PARAMETERS
    CompilerParameters compilerParameters = new CompilerParameters();
    compilerParameters.GenerateInMemory = true;
    compilerParameters.TreatWarningsAsErrors = false;
    compilerParameters.WarningLevel = 4;
    compilerParameters.ReferencedAssemblies.Add("System.dll");

    // COMPILING CODE
    CompilerResults results = codeCompiler.CompileAssemblyFromSource(compilerParameters, code);

    if (results.Errors.Count > 0)
    {
        StringBuilder sbExceptions = new StringBuilder();

        foreach (CompilerError CompErr in results.Errors)
        {
            sbExceptions.AppendLine(
                            "Line number " + CompErr.Line +
                            ", Error Number: " + CompErr.ErrorNumber +
                            ", '" + CompErr.ErrorText + ";" +
                            Environment.NewLine + Environment.NewLine);
        }

        MessageBox.Show("Exception raised while compiling your code: \n\n" + sbExceptions.ToString());
    }
    else
    {
        // GETTING COMPILED ASSEMBLY
        Assembly assembly = results.CompiledAssembly;

        // RETRIEVING COLLECTION CLASS FROM COMPILED ASSEMBLY.
        Type type = assembly.GetType("EntityCollection");

        // CREATING INSTANCE OF COLLECTION CLASS
        IList list = (IList)Activator.CreateInstance(type);

        // RETRIEVING ENTITY CLASS FROM COMPILED ASSEMBLY.
        Type eType = assembly.GetType("Entity");
        object obj = Activator.CreateInstance(eType);

        // ASSIGNING VALUES TO THE ENTITY CLASS OBJECT.
        eType.GetProperty("Name").SetValue(obj, "Sanjay", null);
        eType.GetProperty("Age").SetValue(obj, 30, null);

        // ADDING CLASS TO COLLECTION OBJECT
        list.Add(obj);

        // ASSIGNED LIST TO GRID VIEW AS DATA SOURCE
        dataGridView1.DataSource = list;
    }
}

About code

.NET Framework provides the ICodeCompiler compiler execution interface.The CSharpCodeProvider class implements this interface and provides access to the C# code compiler. The following code creates an instance of CSharpCodeProvider and initializes ICodeCompiler interface object.

CSharpCodeProvider codeProvider = new CSharpCodeProvider();
ICodeCompiler codeCompiler = codeProvider.CreateCompiler();

Now you have the compiler interface which can be used to compile your source code. You need to pass parameters to the compiler by using the CompilerParameters class.

CompilerParameters compilerParameters = new CompilerParameters();
compilerParameters.GenerateInMemory = true;
compilerParameters.TreatWarningsAsErrors = false;
compilerParameters.WarningLevel = 4;
compilerParameters.ReferencedAssemblies.Add("System.dll");

CompilerResults results = codeCompiler.CompileAssemblyFromSource(compilerParameters, code);

CompilerParameters object is used to pass parameters to tell the compiler that you want to generate a class library in memory which can be used further in the code. To compile code you will use function CompileAssemblyFromSource. CompileAssemblyFromSource method takes the compilerParameters object and the source code, which is a string.

Once the code is compiled, you can check to see if there were any compilation errors. You can use the return value from CompileAssemblyFromSource method, which is a CompilerResults object. This object contains an errors collection, which contains any errors that occurred during the compilation process.

Below code collects all the errors in the string builder class which is for demonstration only which you can user for your purpose -

if (results.Errors.Count > 0)
{
    StringBuilder sbExceptions = new StringBuilder();

    foreach (CompilerError CompErr in results.Errors)
    {
        sbExceptions.AppendLine(
                        "Line number " + CompErr.Line +
                        ", Error Number: " + CompErr.ErrorNumber +
                        ", '" + CompErr.ErrorText + ";" +
                        Environment.NewLine + Environment.NewLine);
    }

    MessageBox.Show("Exception raised while compiling your code: \n\n" + sbExceptions.ToString());
}

Once the assembly is generated without any error you can use it in your code. I have used it to generate a list of items and binded this list to the grid view as its data source -

else
    {
        // GETTING COMPILED ASSEMBLY
        Assembly assembly = results.CompiledAssembly;

        // RETRIEVING COLLECTION CLASS FROM COMPILED ASSEMBLY.
        Type type = assembly.GetType("EntityCollection");

        // CREATING INSTANCE OF COLLECTION CLASS
        IList list = (IList)Activator.CreateInstance(type);

        // RETRIEVING ENTITY CLASS FROM COMPILED ASSEMBLY.
        Type eType = assembly.GetType("Entity");
        object obj = Activator.CreateInstance(eType);

        // ASSIGNING VALUES TO THE ENTITY CLASS OBJECT.
        eType.GetProperty("Name").SetValue(obj, "Sanjay", null);
        eType.GetProperty("Age").SetValue(obj, 30, null);

        // ADDING CLASS TO COLLECTION OBJECT
        list.Add(obj);

        // ASSIGNED LIST TO GRID VIEW AS DATA SOURCE
        dataGridView1.DataSource = list;
    }


Once the compilation successes, build assembly can be retrieved from CompileAssemblyFromSource method’s return value which contains CompiledAssembly property.

Now you have the compiled assembly. You can play with it as you wish.

Sunday 12 February 2012

Extension Methods In Dot Net, C#

Sometimes we are in need to extend any class but often it is just not possible to use inheritance if you are working with a value type, a sealed class, or an interface. C# 3 introduces the idea of extension methods.

Extension methods allows an existing type to be extended with new methods without altering the definition of the original type.

SYNTAX

You cannot use any method as the extension method – it has to have the following characteristics:

  • It has to be in a non-nested, non-generic static class and therefore has to be a static method.
  • It has to have at least one parameter where the type of the first parameter will be the type that is extends.
  • The first parameter has to be prefixed with this keyword.

In final words, an extension method is a static method of a static class, where the this modifier is applied to the first parameter. The type of the first parameter will be the type that is extended.

For example:

namespace ExtensionMethods
{
    public static class MyExtensions
    {
        public static int WordCount(this String str)
        {
            return str.Split(new char[] { ' ', '.', '?' },
               StringSplitOptions.RemoveEmptyEntries).Length;
        }
    }  
}

Syntax Explanation

MyExtensions class is a static class and WordCount is a static method of static class. this keyword tells that this is an extension method. Type of first parameter tells it is extending string class.

Usage

The WordCounts extension method can be called as it was an instance method of a string class, as follows:

Console.WriteLine ("Hello Extension Methods".WordCount());

or

string s = "Hello Extension Methods";
int i = s.WordCount();

Note: Extension methods are only in scope when you explicitly import the namespace into your source code with a using directive.

In your code you invoke the extension method with instance method syntax. However, the intermediate language (IL) generated by the compiler translates your code into a call on the static method. Therefore, the principle of encapsulation is not really being violated. In fact, extension methods cannot access private variables in the type they are extending.

The above function’s translation will be as follows:

Console.WriteLine (MyExtensions.WordCount("Hello Extension Methods"));

Extending Interfaces

LINQ has already extended lots of interfaces like, IEnumerable, IEnumerable<T>, IList, IList<T> etc.

A simple syntax of how to extend the interface is:

public static T First<T> (this IEnumerable<T> sequence)
{
    foreach (T element in sequence)
        return element;

    throw new InvalidOperationException ("No elements found!");
}
...
Console.WriteLine ((new int[]{23,453,345}).First());

Extension Method Chaining

Extension methods, like instance methods, provide a tidy way to chain functions. Consider the following two functions:

IList<string> strings = new List<string>();
strings.Add("Hello");
strings.Add("Extension");
strings.Add("Methods");

strings.First().WordCount();

Ambiguity and Resolution

As mentioned above, Extension Methods are only in scope when you explicitly import the namespace into your source code with a using directive. Consider the extension method WordCount, the following application must import ExtensionMethods, in order to avoid a compile-time error:

namespace TestApp
{
    using ExtensionMethods; // importing namespace
    class Test
    {
        static void Main()
        {
            Console.WriteLine ("Hello Extension Methods".WordCount());
        }
    }
}

Extension methods v.s. instance methods

As per the standard preference rule, any compatible instance method will always take precedence over the extension method. In the following example, Test’s Foo method will always take precedence even when called with an argument x of type int:

class Test
{
    public void Foo (object x) { } // This method always wins
}
static class Extensions
{
    public static void Foo (this Test t, int x) { }
}

The only way to call the extension method in this case is via normal static syntax-

Extensions.Foo(...).

Extension methods versus extension methods

If two extension methods have the same signature then the method must be called as an ordinary static method to resolve ambiguity.

If one extension method has more specific arguments, then the more specific method takes precedence. To illustrate, consider the following two classes:

static class StringExtensions
{
    public static int WordCount (this string s) {...}
}
static class ObjectExtensions
{
    public static int WordCount (this object s) {...}
}

The following code calls StringExtensions’s WordCount method:

int wordCount = "Hello Extension Methods".WordCount();

To call ObjectExtensions’s WordCount method, we must specify it explicitly:

int wordCount = (ObjectExtensions.WordCount ("Hello Extension Methods"));

Delegates VS Interfaces

Delegates and interfaces are similar in that they enable the separation of specification and implementation. Multiple independent authors can produce implementations that are compatible with an interface specification. Similarly, a delegate specifies the signature of a method, and authors can write methods that are compatible with the delegate specification. When should you use interfaces, and when should you use delegates?

Delegates are useful when:

  • A single method is being called.
  • A class may want to have multiple implementations of the method specification.
  • It is desirable to allow using a static method to implement the specification.
  • An event-like design pattern is desired (for more information, see the Introduction To Events post).
  • The caller has no need to know or obtain the object that the method is defined on.
  • The provider of the implementation wants to "hand out" the implementation of the
  • specification to only a few select components.
  • Easy composition is desired.

Interfaces are useful when:

  • The specification defines a set of related methods that will be called.
  • A class typically implements the specification only once.
  • The caller of the interface wants to cast to or from the interface type to obtain other interfaces or classes.

Saturday 11 February 2012

Introduction to C# Events

An event is a member that enables an object or class to provide notifications. Clients can attach executable code for events by supplying event handlers. Events work on broadcaster / subscriber model where one object is broadcasting events and other attached objects are subscribing with those events.

Events are declared using event-declarations:

event-declaration:
      event-modifier
event type variable-declaration;

event-modifiers:
    new
    public
    protected
    internal
    private
    static
    virtual
    sealed
    override
    abstract
    extern

An event-declaration may include a set of attributes and access modifiers, the new, static, virtual, override, sealed, abstract, and extern modifiers.

Event declarations are subject to the same rules as method declarations with regard to valid combinations of modifiers.

The type of an event declaration must be a delegate-type, and that delegate-type must be at least as accessible as the event itself.

An event declaration may include event-accessor-declarations. However, if it does not, for non-extern, non-abstract events, the compiler supplies them automatically; for extern events, the accessors are provided externally.

An event declaration that omits event-accessor-declarations defines one or more events—one for each of the variable-declarators. The attributes and modifiers apply to all of the members declared by such an event-declaration.

It is a compile-time error for an event-declaration to include both the abstract modifier and brace-delimited event-accessor-declarations.

When an event declaration includes an extern modifier, the event is said to be an external event. Because an external event declaration provides no actual implementation, it is an error for it to include both the extern modifier and event-accessor-declarations.

An event can be used as the left-hand operand of the += and -= operators. These operators are used, respectively, to attach event handlers to or to remove event handlers from an event, and the access modifiers of the event control the contexts in which such operations are permitted.

Since += and -= are the only operations that are permitted on an event outside the type that declares the event, external code can add and remove handlers for an event, but cannot in any other way obtain or modify the underlying list of event handlers.

In an operation of the form x += y or x -= y, when x is an event and the reference takes place outside the type that contains the declaration of x, the result of the operation has type void (as opposed to having the type of x, with the value of x after the assignment). This rule prohibits external code from indirectly examining the underlying delegate of an event.

The following example shows how event handlers are attached to instances of the Button class:

public delegate void EventHandler(object sender, EventArgs e);
public class Button: Control
{
    public event EventHandler Click;
}
public class LoginDialog: Form
{
    Button OkButton;
    Button CancelButton;
    public LoginDialog()
   
{
        OkButton = new Button(...);
        OkButton.Click += new EventHandler(OkButtonClick);
        CancelButton = new Button(...);
        CancelButton.Click += new EventHandler(CancelButtonClick);
    }
    void OkButtonClick(object sender, EventArgs e)
   
{
        // Handle OkButton.Click event
    }
    void CancelButtonClick(object sender, EventArgs e)
    {
        // Handle CancelButton.Click event
    }
}

Here, the LoginDialog instance constructor creates two Button instances and attaches event handlers to the Click events.

Field-like events

Within the program text of the class or struct that contains the declaration of an event, certain events can be used like fields. To be used in this way, an event must not be abstract or extern, and must not explicitly include event-accessor-declarations. Such an event can be used in any context that permits a field. The field contains a delegate which refers to the list of event handlers that have been added to the event. If no event handlers have been added, the field contains null.

In the example

public delegate void EventHandler(object sender, EventArgs e);
public class Button: Control
{
    public event EventHandler Click;
    protected void OnClick(EventArgs e)
    {
        if (Click != null) Click(this, e);
    }
    public void Reset()
    {
        Click = null;
    }
}

Click is used as a field within the Button class. As the example demonstrates, the field can be examined, modified, and used in delegate invocation expressions. The OnClick method in the Button class “raises” the Click event. The notion of raising an event is precisely equivalent to invoking the delegate represented by the event—thus, there are no special language constructs for raising events. Note that the delegate invocation is preceded by a check that ensures the delegate is non-null.

Outside the declaration of the Button class, the Click member can only be used on the left-hand side of the += and –= operators, as in

b.Click += new EventHandler(…);

which appends a delegate to the invocation list of the Click event, and

b.Click –= new EventHandler(…);

which removes a delegate from the invocation list of the Click event.

When compiling a field-like event, the compiler automatically creates storage to hold the delegate, and creates accessors for the event that add or remove event handlers to the delegate field. In order to be thread-safe, the addition or removal operations are done while holding the lock on the containing object for an instance event, or the type object for a static event.

Thus, an instance event declaration of the form:

class X
{
    public event D Ev;
}

could be compiled to something equivalent to:

class X
{
    private D __Ev;  // field to hold the delegate
    public event D Ev {
        add {
            lock(this) { __Ev = __Ev + value; }
        }
        remove {
            lock(this) { __Ev = __Ev - value; }
        }
    }
}

Within the class X, references to Ev are compiled to reference the hidden field __Ev instead. The name “__Ev” is arbitrary; the hidden field could have any name or no name at all.

Similarly, a static event declaration of the form:

class X
{
    public static event D Ev;
}

could be compiled to something equivalent to:

class X
{
    private static D __Ev;  // field to hold the delegate
    public static event D Ev {
        add {
            lock(typeof(X)) { __Ev = __Ev + value; }
        }
        remove {
            lock(typeof(X)) { __Ev = __Ev - value; }
        }
    }
}

Event accessors

Event declarations typically omit event-accessor-declarations, as in the Button example above. One situation for doing so involves the case in which the storage cost of one field per event is not acceptable. In such cases, a class can include event-accessor-declarations and use a private mechanism for storing the list of event handlers.

The event-accessor-declarations of an event specify the executable statements associated with adding and removing event handlers.

The accessor declarations consist of an add-accessor-declaration and a remove-accessor-declaration. Each accessor declaration consists of the token add or remove followed by a block. The block associated with an add-accessor-declaration specifies the statements to execute when an event handler is added, and the block associated with a remove-accessor-declaration specifies the statements to execute when an event handler is removed.

Each add-accessor-declaration and remove-accessor-declaration corresponds to a method with a single value parameter of the event type and a void return type. The implicit parameter of an event accessor is named value. When an event is used in an event assignment, the appropriate event accessor is used. Specifically, if the assignment operator is += then the add accessor is used, and if the assignment operator is -= then the remove accessor is used. In either case, the right-hand operand of the assignment operator is used as the argument to the event accessor. The block of an add-accessor-declaration or a remove-accessor-declaration must conform to the rules for void methods. In particular, return statements in such a block are not permitted to specify an expression.

Since an event accessor implicitly has a parameter named value, it is a compile-time error for a local variable or constant declared in an event accessor to have that name.

In the example

class Control: Component
{
    // Unique keys for events
    static readonly object mouseDownEventKey = new object();
    static readonly object mouseUpEventKey = new object();
    // Return event handler associated with key
    protected Delegate GetEventHandler(object key) {...}
    // Add event handler associated with key
    protected void AddEventHandler(object key, Delegate handler) {...}
    // Remove event handler associated with key
    protected void RemoveEventHandler(object key, Delegate handler) {...}
    // MouseDown event
    public event MouseEventHandler MouseDown {
        add { AddEventHandler(mouseDownEventKey, value); }
        remove { RemoveEventHandler(mouseDownEventKey, value); }
    }
    // MouseUp event
    public event MouseEventHandler MouseUp {
        add { AddEventHandler(mouseUpEventKey, value); }
        remove { RemoveEventHandler(mouseUpEventKey, value); }
    }
    // Invoke the MouseUp event
    protected void OnMouseUp(MouseEventArgs args) {
        MouseEventHandler handler;
        handler = (MouseEventHandler)GetEventHandler(mouseUpEventKey);
        if (handler != null)
            handler(this, args);
    }
}

the Control class implements an internal storage mechanism for events. The AddEventHandler method associates a delegate value with a key, the GetEventHandler method returns the delegate currently associated with a key, and the RemoveEventHandler method removes a delegate as an event handler for the specified event. Presumably, the underlying storage mechanism is designed such that there is no cost for associating a null delegate value with a key, and thus unhandled events consume no storage.

Static and instance events

When an event declaration includes a static modifier, the event is said to be a static event. When no static modifier is present, the event is said to be an instance event.

A static event is not associated with a specific instance, and it is a compile-time error to refer to this in the accessors of a static event.

An instance event is associated with a given instance of a class, and this instance can be accessed as this in the accessors of that event.

When an event is referenced in a member-access of the form E.M, if M is a static event, E must denote a type containing M, and if M is an instance event, E must denote an instance of a type containing M.

Virtual, sealed, override, and abstract accessors

A virtual event declaration specifies that the accessors of that event are virtual. The virtual modifier applies to both accessors of an event.

An abstract event declaration specifies that the accessors of the event are virtual, but does not provide an actual implementation of the accessors. Instead, non-abstract derived classes are required to provide their own implementation for the accessors by overriding the event. Because an abstract event declaration provides no actual implementation, it cannot provide brace-delimited event-accessor-declarations.

An event declaration that includes both the abstract and override modifiers specifies that the event is abstract and overrides a base event. The accessors of such an event are also abstract.

Abstract event declarations are only permitted in abstract classes.

The accessors of an inherited virtual event can be overridden in a derived class by including an event declaration that specifies an override modifier. This is known as an overriding event declaration. An overriding event declaration does not declare a new event. Instead, it simply specializes the implementations of the accessors of an existing virtual event.

An overriding event declaration must specify the exact same accessibility modifiers, type, and name as the overridden event.

An overriding event declaration may include the sealed modifier. Use of this modifier prevents a derived class from further overriding the event. The accessors of a sealed event are also sealed.

It is a compile-time error for an overriding event declaration to include a new modifier.