Menus

Saturday, January 19, 2013

Check/Uncheck GridView Control Using JavaScript

In this Article, I am explaining how to make use JavaScript in the ASP.Net GridView control and make it more elegant by reducing postbacks.
Functions such as
1.     Highlighting selected row
2.     Check/Uncheck all records using single checkbox.
3.     Highlight row on mouseover event.
The above three functions can be easily achieved using JavaScript thus avoiding postbacks.
 
Basic Concept
 
The basic concept behind this is when the GridView is rendered on the client machine it is rendered as a simple HTML table. Hence what the JavaScript will see a HTML table and not the ASP.Net GridView control.
Hence once can easily write scripts for GridView, DataGrid and other controls once you know how they are rendered.
 
To start with I have a GridView control with a header checkbox and checkbox for each individual record
 
<asp:GridView ID="GridView1" runat="server"  HeaderStyle-CssClass = "header"
AutoGenerateColumns = "false" Font-Names = "Arial"
OnRowDataBound = "RowDataBound"
Font-Size = "11pt" AlternatingRowStyle-BackColor = "#C2D69B" >
<Columns>
<asp:TemplateField>
    <HeaderTemplate>
      <asp:CheckBox ID="checkAll" runat="server" onclick = "checkAll(this);" />
    </HeaderTemplate>
   <ItemTemplate>
     <asp:CheckBox ID="CheckBox1" runat="server" onclick = "Check_Click(this)" />
   </ItemTemplate>
</asp:TemplateField>
<asp:BoundField ItemStyle-Width="150px" DataField="CustomerID" HeaderText="CustomerID"  />
<asp:BoundField ItemStyle-Width="150px" DataField="City"
HeaderText="City" />
<asp:BoundField ItemStyle-Width="150px" DataField="Country"
HeaderText="Country"/>
<asp:BoundField ItemStyle-Width="150px" DataField="PostalCode"  HeaderText= "PostalCode"/>
</Columns>
</asp:GridView>

Above you will notice I am calling two JavaScript functions checkAll and Check_Click which I have explained later. Also I have attached a RowDataBound event to the GridView to add mouseover event
 
  
  
Highlight Row when checkbox is checked
 
<script type = "text/javascript">
function Check_Click(objRef)
{
    //Get the Row based on checkbox
    var row = objRef.parentNode.parentNode;
    if(objRef.checked)
    {
        //If checked change color to Aqua
        row.style.backgroundColor = "aqua";
    }
    else
    {   
        //If not checked change back to original color
        if(row.rowIndex % 2 == 0)
        {
           //Alternating Row Color
           row.style.backgroundColor = "#C2D69B";
        }
        else
        {
           row.style.backgroundColor = "white";
        }
    }
   
    //Get the reference of GridView
    var GridView = row.parentNode;
   
    //Get all input elements in Gridview
    var inputList = GridView.getElementsByTagName("input");
   
    for (var i=0;i<inputList.length;i++)
    {
        //The First element is the Header Checkbox
        var headerCheckBox = inputList[0];
       
        //Based on all or none checkboxes
        //are checked check/uncheck Header Checkbox
        var checked = true;
        if(inputList[i].type == "checkbox" && inputList[i] != headerCheckBox)
        {
            if(!inputList[i].checked)
            {
                checked = false;
                break;
            }
        }
    }
    headerCheckBox.checked = checked;
   
}
</script>

The above function is invoked when you check / uncheck a checkbox in GridView row
First part of the function highlights the row if the checkbox is checked else it changes the row to the original color if the checkbox is unchecked.
The Second part loops through all the checkboxes to find out whether at least one checkbox is unchecked or not.
If at least one checkbox is unchecked it will uncheck the Header checkbox else it will check it
 
 
Check all checkboxes functionality
 
<script type = "text/javascript">
function checkAll(objRef)
{
    var GridView = objRef.parentNode.parentNode.parentNode;
    var inputList = GridView.getElementsByTagName("input");
    for (var i=0;i<inputList.length;i++)
    {
        //Get the Cell To find out ColumnIndex
        var row = inputList[i].parentNode.parentNode;
        if(inputList[i].type == "checkbox"  && objRef != inputList[i])
        {
            if (objRef.checked)
            {
                //If the header checkbox is checked
                //check all checkboxes
                //and highlight all rows
                row.style.backgroundColor = "aqua";
                inputList[i].checked=true;
            }
            else
            {
                //If the header checkbox is checked
                //uncheck all checkboxes
                //and change rowcolor back to original
                if(row.rowIndex % 2 == 0)
                {
                   //Alternating Row Color
                   row.style.backgroundColor = "#C2D69B";
                }
                else
                {
                   row.style.backgroundColor = "white";
                }
                inputList[i].checked=false;
            }
        }
    }
}
</script> 
 
The above function is executed when you click the Header check all checkbox When the Header checkbox is checked it highlights all the rows and checks the checkboxes in all rows.
And when unchecked it restores back the original color of the row and unchecks the checkboxes.
 
Note:
The check all checkboxes checks all the checkboxes only for the current page of the GridView and not all.
 
Highlight GridView row on mouseover event
 
<script type = "text/javascript">
function MouseEvents(objRef, evt)
{
    var checkbox = objRef.getElementsByTagName("input")[0];
   if (evt.type == "mouseover")
   {
        objRef.style.backgroundColor = "orange";
   }
   else
   {
        if (checkbox.checked)
        {
            objRef.style.backgroundColor = "aqua";
        }
        else if(evt.type == "mouseout")
        {
            if(objRef.rowIndex % 2 == 0)
            {
               //Alternating Row Color
               objRef.style.backgroundColor = "#C2D69B";
            }
            else
            {
               objRef.style.backgroundColor = "white";
            }
        }
   }
}
</script>
 
The above JavaScript function accepts the reference of the GridView Row and the event which has triggered it.
Then based on the event type if event is mouseover it highlights the row by changing its color to orange else if the event is mouseout it changes the row’s color back to its original before the event occurred.
The above function is called on the mouseover and mouseout events of the GridView row. These events are attached to the GridView Row on the RowDataBound events refer the code below
  
      
C#
protected void RowDataBound(object sender, GridViewRowEventArgs e)
{
    if (e.Row.RowType == DataControlRowType.DataRow )
    {
        e.Row.Attributes.Add("onmouseover","MouseEvents(this, event)");
        e.Row.Attributes.Add("onmouseout", "MouseEvents(this, event)"); 
    }
}
 
VB.Net
Protected Sub RowDataBound(ByVal sender As Object,
ByVal e As GridViewRowEventArgs)
  If e.Row.RowType = DataControlRowType.DataRow Then
     e.Row.Attributes.Add("onmouseover", "MouseEvents(this, event)")
     e.Row.Attributes.Add("onmouseout", "MouseEvents(this, event)")
  End If
End Sub
 

You can try out the functionality discussed above using the sample GridView here


CustomerIDCityCountryPostalCode
ALFKIBerlinGermany12209
ANATRMéxico D.F.Mexico05021
ANTONMéxico D.F.Mexico05023
AROUTLondonUKWA1 1DP
BERGSLuleåSwedenS-958 22
BLAUSMannheimGermany68306
BLONPStrasbourgFrance67000
BOLIDMadridSpain28023
BONAPMarseilleFrance13008
BOTTMTsawassenCanadaT2F 8M4

Friday, December 28, 2012

LINQ in C# : How does it work? - Part II

Introduction

In the first part of How does it work in C#? article I discussed about the var, auto-implemented properties and += and -= of events in C#. In this article, I will be discussing about throwing an exception and how does it work and what is the internal mechanism for these, what is internal working mechanism of where and select clause of Enumerable class in C#.

How does it work?

In the following discussion we will see the internal working mechanism about the throw statement and where, select of Enumerable class.
throw an exceptionObject
Exception is one of the common scenarios for any application. As a result, proper exception management is one of the important tasks in application development. In here, I would like to discuss about throw statement in C#, how does throw anExceptionObject; or just throw works. I wrote a small program which will help me to explain the details. The task of this program is simple, it will raise an exception and throw it,
namespace TestHarnessPart2
{
    using System;
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                Person person = new Person();
            }
            catch (Exception exceptionObject)
            {
                throw;
            }
        }
    }
    public class Person
    {
        public Person()
        {
            throw new Exception("Exception from Person.");
        }
    }
}
The above code catch the exception and just throw it with out passing any explicit exception object.I created another version of this above code for example,
namespace TestHarnessPart2
{
    using System;
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                Person person = new Person();
            }
            catch (Exception exceptionObject)
            {
                throw exceptionObject;
            }
        }
    }
    public class Person
    {
        public Person()
        {
            throw new Exception("Exception from Person.");
        }
    }
}
The above code will create a Person object and if any exception occurred during this creation it will catch that exception and from the catch block it throws the catched exception which is throw exceptionObject;. Now if we see the stack trace of those above code we will find out there is difference, stack trace for first version of the code is as below,

HowitworksCSharpTwo/StackTraceThrow.PNG

Fig: Stack trace of the first version of the code.
And stack trace of the second version of the code,

HowitworksCSharpTwo/StackTraceThrowException.PNG

Fig: Stack trace of the second version of the code
From the above two images we can see the details of the stack trace is different.Now the question is how does it happens, to get the answer we will get help of ILDasm program, grab the exe of TestHarnessPart2 from the bin folder and drop into the ILDasm program , for the first version of the code we will see something like below,
HowitworksCSharpTwo/ILThrow.PNG
Fig :IL code for using just throw in the catch block.
From the above image we can see exceptionObject has been defined as local variable using .locals init [1] but in the catch block compiler change the throw statement into
catch [mscorlib]System.Exception 
{
   IL_000b:  stloc.1
   IL_000c:  nop
   IL_000d:  rethrow
}  // end handler
rethrow, which means compiler does not change the original stack trace of the exception object it is re- throwing the existing one, where as if we look into the following image for the second version of the above C# code,
HowitworksCSharpTwo/ILThrowExceptionObject.PNG
Fig: IL code for using just throw excetpionObject in the catch block.
We can see exceptionObject has been defined as local variable as well, using the same way for the first version of the code for example, .locals init [1]. But in the catch block it is doing totally different stuffs compare to first version of the above code. In the catch block compiler load location of 1( ldloc.1) which is the exceptionObject
  catch [mscorlib]System.Exception
  {
    IL_000b:  stloc.1
    IL_000c:  nop
    IL_000d:  ldloc.1
    IL_000e:  throw
  }  // end handler
and throw that one. As a result, this exceptionObject will not hold all the stack trace raised earlier except the stack trace from this current state. Now the last bit of the puzzle is, what actually throw and rethrow statement does? From the Partition III CIL.doc retrieved from ECMA C# and Common Language Infrastructure Standards we can see what actually throw and rethrow does,
IL Instruction Description
throw Throw an exception. The throw instruction throws the exception object (type O) on the stack and empties the stack. So in relation to the second version of the code block it will be,
[1] class [mscorlib]System.Exception 
exceptionObject
rethrow Rethrow the current exception. The rethrow instruction is only permitted within the body of a catch handler. It throws the same exception that was caught by this handler. A rethrow does not change the stack trace in the object.

So it is clear that throw exceptionObject override the stack trace where as, just throw statement does not override the stack trace.
Where and Select of Enumerable
Before we start discussing about Where and Select, just have a quick look of the where and select clause’s signature,
HowitworksCSharpTwo/WhereSignature.PNG
Fig: Signature of the Where clause
HowitworksCSharpTwo/SelectSignature.PNG
Fig: Signature of the Select clause
Where and Select is two extension methods of Enumerable class in .Net. Following sections will discuss about the internal of Where and Select, how does it work. Before, we go ahead just bit of heads up about Func in .Net. According to MSDN, we can use this delegate to represent a method that can be passed as a parameter without explicitly declaring a custom delegate. The encapsulated method must correspond to the method signature that is defined by this delegate. A simple example of Func below,
namespace TestHarnessPart2
{
    using System;
    public class ExampleOfFunc
    {
        public int TestFunc(string dataToCheck, Func<string,> getLength)
        {
            return getLength(dataToCheck);
        }
    }
}
and to test the above code,
private static void TestFuncExample()
{
    ExampleOfFunc exampleOfFunc = new ExampleOfFunc();
    Console.WriteLine(exampleOfFunc.TestFunc("Example of Func", (dataToTest) => dataToTest.Length));
}
So from the above code we can see TestFunc method accepting a Func as parameter and execute it just by doing return getLength(dataToCheck);, from the caller of this method is actually passing an anonymous method block for the Func parameter. Through out the entire discussion about where and select we will find this concept used many places,
Before we go ahead I like to show a bit of code created to explain the where and select statement,
namespace TestHarnessPart2
{
    using System.Collections.Generic;
    using System.Linq;
    class WhereSelect
    {
        List<string> bookList = new List<string>() { "Einstein: His Life and Universe", "Ideas And Opinions"
"The World As I See It " };
        public IEnumerable<string> GetBookListWhichLengthIsGreaterThan(int lengthOfTheBookName)
        {
            //return bookList.Where(book => book.Length == lengthOfTheBookName).Select(book => book);
            return bookList.Where(book =>
            {
                var currentBook = book;
                return book.Length > lengthOfTheBookName;
            }).Select(book =>
            {
                var currentBook = book;
                return book;
            });
        }
    }
}
The above code is not doing much rather check the booklist whether it has book which name length is greater than given length (lengthOfTheBookName). Now we will try to find out bit of internal working mechanism of Where and Select clause, where and select clause is defined in the Enumerable class and internal of the where clause is as below,
HowitworksCSharpTwo/WhereInternal.PNG
Fig: Where internal
From the above image we see Where method is calling another internal private class named WhereListIterator<T> which is doing all the work for us which is filter the booklist List based on the condition provided and return output.
private class WhereListIterator<tsource> : Enumerable.Iterator<tsource>
{
      public override bool MoveNext()
      {
        // most of the code has been removed for simplicity
        while (this.enumerator.MoveNext())
        {
          TSource current = this.enumerator.Current;
          if (this.predicate(current))
          {
            base.current = current;
            return true;
          }
        }
      }
}
From the above code we can see this.predicate(current)) line of code is actually executing the Func or anonymous method block in this case, <GetBookListWhichLengthIsGreaterThan>b__0 method. On the other hand, select clause is working like below,
HowitworksCSharpTwo/SelectInternal.PNG
Fig: Select internal
Also, from the above image we can see that there is one selector which is actually <GetBookListWhichLengthIsGreaterThan>b__1 method to select the item from the list. Now need to find out where is this <GetBookListWhichLengthIsGreaterThan>b__1 and
<GetBookListWhichLengthIsGreaterThan>b__0
coming from? Please have a look image below, from the following image we can there is two method named <GetBookListWhichLengthIsGreaterThan>b__1 used for selector and
<GetBookListWhichLengthIsGreaterThan>b__0 used for predicate of the where clause.
HowitworksCSharpTwo/OutputOfTheWhereSelectWithPredicates.PNG
Fig: The output of WhereSelect test
Where and Select clause is using those two methods for doing the operation. Bit of more investigation to find out about it. Now we need the help of .Net Reflector and ILDasm. First grab the TestHarnessPart2.exe from the bin folder and drop into .Net Reflector, then we will find out following interesting stuff, a class <>c__DisplayClass3 with following details,
[CompilerGenerated]
private sealed class <>c__DisplayClass3
{
    // Fields
    public int lengthOfTheBookName;
    // Methods
    public bool <GetBookListWhichLengthIsGreaterThan><getbooklistwhichlengthisgreaterthan>b__0(string book)
    {
        string currentBook = book;
        return (book.Length > this.lengthOfTheBookName);
    }
}
From the class we can see a method named <GetBookListWhichLengthIsGreaterThan>b__0 which is actually containing the filtering condition we defined in the where clause,
{
     var currentBook = book;
     return book.Length > lengthOfTheBookName;
}
And also there is also another method (outside of <>c__DisplayClass3 class) named <GetBookListWhichLengthIsGreaterThan>b__1
[CompilerGenerated]
private static string <GetbookListWhichLengthIsGreaterThan><getbooklistwhichlengthisgreaterthan>b__1(string book)
{
    string CS$<>8__locals4 = book;
    return book;
}
Which is actually containing the statement we defined for the select clause as below,
{
     var currentBook = book;
     return book;
}
Now to prove the above stuff, we will look at the IL code retrieved from ILDasm(also grab the TestHarnessPart2.exe from the bin folder and drop into ILDasm program) for the above C# code,

HowitworksCSharpTwo/ILCodeForWhereSelect.PNG
Fig: IL code of the GetBookListWhichLengthIsGreaterThan method
From the above image we can see where clause is using <GetBookListWhichLengthIsGreaterThan>b__0 method of <>c__DisplayClass3 class and select clause is using <GetBookListWhichLengthIsGreaterThan>b__1 method. So it is clear now how does it work.
Limitation
  • There are no discussions about Query Expression and Lambda Expression.

LINQ in C# : How does it Work? - Part I

Introduction

The C# language of the .NET(MSDN), has many features such as var to declare variable, implement the properties automatically and many more which makes life of a programmer easier. On the other hand, this abstraction creates a bit of confusion such as how does it work, how .Net implemented those stuffs in behind the scene. In this article, I will try to find out few of those stuff for example, var, Auto-Implemented properties and += or -= syntax used in Events.

How does it work?

In the following discussion, we will see how does var, auto-implemented properties and += and -= of Events work
var and details
In C#, it is possible to declare variable in few ways, implicit type declaration using var keyword is one of those. For example, it is possible to use var totalSalary =0; instead of int totalSalary =0;. As MSDN article suggested var is strongly typed but compiler determined the types. To explore it a bit more, I created a small class,
namespace TestHarness
{
    public class VarExplorer
    {
        public Book ExploreVar()
        {
            var myBook = new Book() { Name = "What is out there?" };
            myBook.Name = "What is out there?";
            return myBook;
        }
    }
    public class Book
    {
        public string Name { get; set; }
    }
}
The above class is not doing much rather initialising an instance of type Book which contains a method named ExplorerVar. This method has variable declared using var. In the design time or coding or while writing code in the IDE, hover the mouse just above the keyword var then the compiler will determine the types of variable myBook. Please have a look the image below,

How_does_work_in_CSharp/VarAtDesignTime.PNG

Fig: var at design time
So it is clear that compiler determine the types at design time. Another question is what is happening at runtime? To test, I compiled the TestHarness project and grab the TestHarness.exe from the bin folder and drop into .Net Reflector program, using the help .Net Reflector the code I found out for ExploreVar method is as below,
public Book ExploreVar() 
{
    Book <>g__initLocal0 = new Book {

        Name = "What is out there?"

    };
    Book myBook = <>g__initLocal0;
    myBook.Name = "What is out there?";
    return myBook; 
}
The above code clearly shows that compiler replace var keyword with appropriate type while building project as exe. From the above discussion, it is clear how does var work in C#.
Auto-Implemented Properties
In C# class declaration is pretty easy we declare a class using following syntax,
public class Book 
{
       private string name;
       public string Name  
       {
            get {
                return name;
            }
            set {
                name = value;
            }
       }
}
It is really simple but from C# 3.0 it is more easier. We can declare Book class as below,
public class Book
{ 

   public string Name { get; set; }

}
Now the question is what is the difference between that syntax used to declare a class. In reality there is nothing except compiler doing all the work for us. A Bit of background of class, the concept of the Encapsulation has been implemented in different way. First declaration of Book class, the encapsulation has done by introducing Properties which actually equivalent to Get and Set method. So it is not possible to directly access the private members of the class unless there is a Public Properties or Get/Set method.
Now for the second syntax of the Book declaration, compiler is doing all this encapsulation on behalf of a programmer. When decompile second version of Book using .Net Reflector, Compiler itself generates get and set methods for Name property and declare a variable name <Name>k__BackingField ( in here <Name> is actuall property name defined in Book class ),
So whole decompiled code for Name is as below,
private string <name>k__BackingField;

public void set_Name(string value)
{
    this.<name>k__BackingField = value;
}

public string get_Name()
{
    return this.<name>k__BackingField;
}
From the above discussion clear that how compiler handle auto implemented properties. A bit more investigation at runtime, I created following code snippet to test the auto implemented properties stuff at run time,
    using System.Collections.Generic;
    using System.Linq;
    using System.Reflection;

    public class AutoImplementatedProperties
    {
        public IEnumerable<string> ExploreMembers(Person personObject)
        {
        return personObject.GetType().GetMembers().ToList<memberinfo>().Select(memberInfo => 
memberInfo.Name);
        }

        public IEnumerable<string> ExplorePrivateFields(Person personObject)
        {
            return personObject.GetType().GetFields(BindingFlags.NonPublic |
 BindingFlags.Instance).Select(fieldInfo => string.Format("{0,40}-->{1,20}", fieldInfo.Name, 
fieldInfo.GetValue(personObject).ToString()));
        }
        public Person CreateATestObject()
        {
            Person personObject = new Person()
            {
                Name = "M",
                Profession = "Coding"
            };
            return personObject;
        }
    }
    public class Person
    {
        public string Name { get; set; }
        public string Profession { get; set; }
    }
}
In the above class, ExploreMembers method will explore all the members of Person object and ExplorePrivateFields will explore all the private fields including their value of Person object. If we run the ExplorePrivateFields method then we can see it will display,
How_does_work_in_CSharp/OutputOfAutoImplementedProperties.PNG
Fig: Auto-implemented properties test result.
Above output and now if compare with Person instantiation code below,
public Person CreateATestObject()
{
    Person personObject = new Person()
    {
        Name = "M",
        Profession = "Coding"
    };
    return personObject;
}
It is clear that How C# store Name and Profession property values at runtime.
+= and -= in Events
The definition of Event has been taken from MSDN. An event in C# is a way for a class to provide notifications to clients of that class when some interesting thing happens to an object.
The main point of this discussion is to discuss about, += (add) or -= (remove) syntax and how does it relates to the Event and delegate and also how does it work? Actually, the compiler translate += or -= into add or remove method in Layman’s terms. So to test the concept, I created small class (which is doing pretty much nothing),
public class LogIt
{
    public delegate void LogHandler(string message);
    public event LogHandler Log;
    public Delegate[] GetList()
    {
        return Log.GetInvocationList();
    }
}
So the above class has a delegate name LogHandler and event named Log of type LogHandler. The consumer of this class is as below,
public class AddRemoveHandlerOfEvent
{
    public LogIt InitialiseLogIt()
    {
        LogIt logItObject = new LogIt();
        logItObject.Log += new LogIt.LogHandler(Logger1);
        logItObject.Log += new LogIt.LogHandler(Logger2);
        logItObject.Log += new LogIt.LogHandler(Logger3);
        return logItObject;
    }

    public void Logger1(string s)
    {
        Console.WriteLine(s);
    }

    public void Logger2(string s)
    {
        Console.WriteLine(s);
    }

    public void Logger3(string s)
    {
        Console.WriteLine(s);
    }
}
So from the above consumer class we can see few methods such as Logger1, Logger2, Logger3 has been added to the Log of LogIt object. Now the question is where does these methods information stored or how does it works while use += or -= syntax. To answer the question again need to get help of .Net reflector. When we reflect the code we can see following code has generated by compiler for += or -=,
public class LogIt
{
    // Nested Types
    public delegate void LogHandler(string message);
    // Fields
    private LogHandler Log;
    // Events
    public event LogHandler Log
    {
        add
        {
            LogHandler handler2;
            LogHandler log = this.Log;
            do
            {
                handler2 = log;
                LogHandler handler3 = (LogHandler) Delegate.Combine(handler2, value);
                log = Interlocked.CompareExchange<loghandler>(ref this.Log, handler3, handler2);
            }
            while (log != handler2);
        }
        remove
        {
            LogHandler handler2;
            LogHandler log = this.Log;
            do
            {
                handler2 = log;
                LogHandler handler3 = (LogHandler) Delegate.Remove(handler2, value);
                log = Interlocked.CompareExchange<loghandler>(ref this.Log, handler3, handler2);
            }
            while (log != handler2);
        }
    }
    // Methods
    public Delegate[] GetList()
    {
        return this.Log.GetInvocationList();
    }
}
There are two new stuffs which is add and remove, need to explore now does add and remove works, from add block one line of code give us all the clue which is,
LogHandler handler3 = (LogHandler) Delegate.Combine(handler2, value);
and if we dig bit more with Combine method, we will find out something like below,
public static Delegate Combine(Delegate a, Delegate b)
{
    if (a == null)
    {
      return b;
    }
    return a.CombineImpl(b);
}
The code block is from the Delegate class and also CombineImpl method called from Combine method is defined in the MulticastDelegate class.
The MulticastDelegate holds two variable
private IntPtr _invocationCount; 
private object _invocationList; 

and this _invocationList is the main object which holds all the method signature assigned using += syntax. From the below images we can see how does _ invocationList used to store all the method signature as method pointer(IntPtr) How_does_work_in_CSharp/ObjectAsInvocationList.PNG
Fig: _innvocationList of MulticastDelegate class

How_does_work_in_CSharp/ObjectHoldInvocationList.PNG
Fig: objArray at runtime.

How_does_work_in_CSharp/EventAtRuntimeResized.PNG
Fig: In depth view of the objArray to find out method information from _ innvocationList
To get a clear view of this, I created a small test program to test, how .Net store all the method inside the _invocationList. If we see the below code block of the CombineImpl method,
public IEnumerable<string> GetInitialMethodPointer()
{
    return GetType().GetMethods().Select(methodInfo => string.Format("{0,40}-->{1,20}",
 methodInfo.Name,methodInfo.MethodHandle.Value.ToString()));
}

public IEnumerable<string> GetInvocationListFrom(LogIt logItObject)
{
    return logItObject.GetList().Select(delegateObject => string.Format("{0,40}-->{1,20}",
 delegateObject.Method.Name,
 delegateObject.Method.MethodHandle.Value.ToString()));
}
</string></string>
We can see, GetInitialMethodPointer method will show the initial methods pointer for Logger1, Logger2 and Logger3 and GetInvocationListFrom method will show what does _invocationList contains after using += operation to add method signneture. From the image below, it is clear that _invocationList contains exactly the same method pointer value we get from GetInitialMethodPointer.
How_does_work_in_CSharp/EventsOutput.PNG
Fig: Output of the method pointer store into _ innvocationList

The implementation of -= is also a bit of nice stuff to dissect. Following Call Stack will show how the remove calls from consumer code to all the way back into Delegate class to update _ invocationList list,
How_does_work_in_CSharp/RemoveEvents.PNG


Fig: Call Stack of Delete Operation


And if we look into the RemoveImpl method code then we can see it called another method named DeleteFromInvocationList as below,

  How_does_work_in_CSharp/RemoveInitialCode.PNG

Fig: Inside of the RemoveImpl method


And code inside DeleteFromInvocationList will eventually update the _ invocationList list to update the current method signature after remove. Please see the following image,
How_does_work_in_CSharp/InsideOfDeleteOperation.PNG

Fig: Inside of DeleteFromInvocationList
So from the above discussion we have pretty good picture of how += and -= works for Events and Delegate.
If you interested detail about other features of the C# language, Expert C# 5.0 with the .NET 4.5 Framework might be useful.