Thursday, April 12, 2012

- Further Advanced Features

Indexer Methods:
By defining an indexer method to your custom classes and structures you may give indexer capability, that behaves just like a standard array. In another word Indexers allow you to access items in an array like fashion. Let's add indexer support to ShepeCollection type developed in Generic chapter:

public class ShapeCollection : IEnumerable
{
    //declare a private ArrayList
    private ArrayList shape_array = new ArrayList();

    public Shape this[int index]
    {
        get { return (Shape)shape_array[index]; }
        set { shape_array.Insert(index, value); }
    }
    // ...
}

As you see apart from this keyword is just like C# property declaration. You can use above class in array like fashion as follow:

static void Main(string[] args)
{
    ShapeCollection shapes = new ShapeCollection();
    shapes[0] = new Shape();
    shapes[1] = new Shape();
    //..
}

It is always better for custom collection to support indexer methods in order to be able to integrate well into the fabric of the .NET base class libraries.
Indexer example for string values:

private Dictionary<string, Shape> shape_dictionary =
        new Dictionary<string,Shape>();
//returns a shape based on a string indexer.
public Shape this[string name]
{
    get { return (Shape)shape_dictionary[name]; }
    set { shape_dictionary[name] = value; }
}

Here is another example for Multiple Dimensions:

private int[,] TowDArray = new int[10, 10];
public int this[int row, int column]
{ /*...*/    }

Indexer example for interface:

public interface IStringContainer
{
    //an indexer that returns strings based on a numerical index
    string this[int index] { get; set; }
}

Operator Overloading:
C# provides the operator keyword, which you can use only in conjunction with static methods.
  • "like" operators (i.e. < and >, <= and => , == and !=) are overloaded together.
  • [] , () and shorthand assignment operators (+=, -= ,*=, /=, %=, &=, |=, ^=, <<=, >>=) cannot be overloaded.  
Keep in mind, the shorthand assignment operators are automatically simulated if a type overloads the related binary operator. Let's go through a complete example:

public class Point : IComparable
{
    public int X { get; set; }
    public int Y { get; set; }

    public Point(int xPos, int yPos)
    {
        X = xPos;
        Y = yPos;
    }

    public override string ToString()
    {
        return string.Format("[{0}, {1}]", this.X, this.Y);
    }

    //Binary ops
    // overloaded operator +
    public static Point operator +(Point p1, Point p2)
    { return new Point(p1.X + p2.X, p1.Y + p2.Y); }

    // overloaded operator -
    public static Point operator -(Point p1, Point p2)
    { return new Point(p1.X - p2.X, p1.Y - p2.Y); }

    public static Point operator +(Point p1, int change)
    {
        return new Point(p1.X + change, p1.Y + change);
    }

    public static Point operator +(int change, Point p1)
    {
        return new Point(p1.X + change, p1.Y + change);
    }

    //Unary ops
    // Add 1 to the X/Y values incoming Point.
    public static Point operator ++(Point p1)
    { return new Point(p1.X + 1, p1.Y + 1); }

    // Subtract 1 from the X/Y values incoming Point.
    public static Point operator --(Point p1)
    { return new Point(p1.X - 1, p1.Y - 1); }

    //Equality logic
    public override bool Equals(object o)
    {
        return o.ToString() == this.ToString();
    }

    public override int GetHashCode()
    { return this.ToString().GetHashCode(); }

    // Now let's overload the == and != operators.
    public static bool operator ==(Point p1, Point p2)
    { return p1.Equals(p2); }

    public static bool operator !=(Point p1, Point p2)
    { return !p1.Equals(p2); }

    //Compare ops
    public int CompareTo(object obj)
    {
        if (obj is Point)
        {
            Point p = (Point)obj;
            if (this.X > p.X && this.Y > p.Y)
                return 1;
            if (this.X < p.X && this.Y < p.Y)
                return -1;
            else
                return 0;
        }
        else
            throw new ArgumentException();
    }

    public static bool operator <(Point p1, Point p2)
    { return (p1.CompareTo(p2) < 0); }

    public static bool operator >(Point p1, Point p2)
    { return (p1.CompareTo(p2) > 0); }

    public static bool operator <=(Point p1, Point p2)
    { return (p1.CompareTo(p2) <= 0); }

    public static bool operator >=(Point p1, Point p2)
    { return (p1.CompareTo(p2) >= 0); }
}

And here is Main methods to illustrate the usage of above example:

static void Main(string[] args)
{
    Point ptOne = new Point(100, 100);
    Point ptTwo = new Point(40, 40);
    Console.WriteLine("ptOne = {0}", ptOne);
    Console.WriteLine("ptTwo = {0}", ptTwo);

    // Add the points to make a bigger point?
    //Pseudo-code: Point.operator+ (ptOne, ptTwo)
    Console.WriteLine("ptOne + ptTwo: {0} ", ptOne + ptTwo);

    // Subtract the points to make a smaller point?
    Console.WriteLine("ptOne - ptTwo: {0} ", ptOne - ptTwo);

    // Freebie +=
    Point ptThree = new Point(90, 5);
    Console.WriteLine("ptThree = {0}", ptThree);
    Console.WriteLine("ptThree += ptTwo: {0}", ptThree += ptTwo);

    // Freebie -=
    Point ptFour = new Point(0, 500);
    Console.WriteLine("ptFour = {0}", ptFour);
    Console.WriteLine("ptFour -= ptThree: {0}", ptFour -= ptThree);

    // Applying the ++ and -- unary operators to a Point.
    Point ptFive = new Point(1, 1);
    Console.WriteLine("++ptFive = {0}", ++ptFive);  // [2, 2]
    Console.WriteLine("--ptFive = {0}", --ptFive);  // [1, 1]

    // Apply same operators as post increment/decrement.
    Point ptSix = new Point(20, 20);
    Console.WriteLine("ptSix++ = {0}", ptSix++);  // [20, 20]
    Console.WriteLine("ptSix-- = {0}", ptSix--);  // [21, 21]

    Console.WriteLine("ptOne == ptTwo : {0}", ptOne == ptTwo);
    Console.WriteLine("ptOne != ptTwo : {0}", ptOne != ptTwo);

    Console.WriteLine("ptOne < ptTwo : {0}", ptOne < ptTwo);
    Console.WriteLine("ptOne > ptTwo : {0}", ptOne > ptTwo);

    Console.ReadLine();
}
operator overloading example output
Custom Type Conversion:
Explicit casting doesn't work when types are not related by classical inheritance. However C# let you build custom conversion routines that allow your types to respond to the () casting operator.

//type can explicitly convert to another type
public static explicit operator CurrentType(TypeYouWantToConvertToCurrentType new_type)
{
    CurrentType c = new CurrentType();
    //code for conversion
    return c;
}
  • You cannot define an implicit and explicit conversion routine
  • However when a type defines an implicit conversion routine you can make  use of the explicit cast syntax
//type can implicitly convert to another type
public static implicit operator CurrentType(TypeYouWantToConvertToCurrentType new_type)
{
    CurrentType c = new CurrentType();
    //code for conversion
    return c;
}

Extension Methods:
Allow you to tack on new functionality to precompiled  files on the fly without needing to directly update the file being extended. As you now the type definition is final once a type defined and complied into a .NET assembly. The only way to add new member, update member, or remove members is to recode and recompile the code base onto an updated assembly (or using the System.Reflection.Emit namespace to dynamically reshape a compiled type in memory).
  • cannot modify the original type declaration, it can just inject new functionality.
  • must be defined within a static.
  • first parameter always get the marked by this keyword modifier
  • can be called from correct instance in memory or statically via the defining static class.
  • always the first parameter of an extension method represents the type being extended.
As an example lets create MyExtnensions static class and add ReversDigits Extension method for integers:

static class MyExtensions
{
  // This method allows any integer to reverse its digits.
  public static int ReverseDigits(this int i)
  {
    // Translate int into a string, and then
    // get all the characters.
    char[] digits = i.ToString().ToCharArray();

    // Now reverse items in the array.
    Array.Reverse(digits);

    // Put back into string.
    string newDigits = new string(digits);

    // Finally, return the modified string back as an int.
    return int.Parse(newDigits);
  }
}

you may call the above extension method through an integer like below:
                              int myInt = 113344;
                              myInt.
IntelliSense of extension method
Calling an extension method from an object is just some smoke-and-mirrors effect provided by the compiler.  Extension methods are limited to the namespace that define them or the namespaces that import them. Keep in mind when you extend an interface with new members, you must also supply an implementation of these members.

Partial Methods:
It will allow you to prototype a method in one file, yet implement it in another file.
  • Can only be defined in partial class.
  • Must Return void.
  • Cannot have arguments with the out modifier.
  • May or may not emitted into the compiled assembly. 
In the following example there are tow files "Shape.cs" and ShapeImp.cs" which contains the shape class and Draw method have been implemented using partial methods:

//file: "Shape.cs"
partial class Shape
{
    //A "lightweight" event handler.
    partial void OnDraw();
    public void myDraw()
    {
        Draw();
    }  
}


//file: "ShapeImp.cs"
partial class Shape
{
    partial void OnDraw()
    {
        Console.WriteLine("Draw called from \"ShapeImp.cs\" file");
    }
}

When a method is defined with the partial keyword, the compiler will determine if it should be emitted into the assembly based on whether the method has a method body or is simply an empty signature.
We can say C# partial methods are a strongly typed version of conditional code compilation (via the #if, #elif, #else, and #endif preprocessor directives). However partial method will be completely ignored during the compilation cycle. By marking a method with the partial modifier, other class builders have the option of providing implementation details if they so choose. Which is much cleaner solution than using preprocessor directives, supplying "dummy"implementations to virtual methods, or throwing NotImplementedException objects.
Light weight events is one of the most common use of this syntax which allows class designers to provide method hooks, similar to event handlers, that developers may choose to implement or not. (usually with On prefix).
In above example if any calls buileders wish to be informed when the myDraw() method has been called, they can provide an implementation of the OnDraw() method. If they do not care, they simply do nothing.

Anonymous Types:
Is useful when you need a temporary type in order to model a set of encapsulated data points without any associated methods, events, or other custom functionality. Anonymous types are marked with var keyword in conjunction with object initialization syntax. Consider the following example:

//make an anonymous type representing a task
var myTask = new {name ="Simple Task", load = 1000};
Console.WriteLine("This is \"{0}\" task with \"{1}\" load", myTask.name, myTask.load);

Console.WriteLine("The type is {0}", myTask.GetType().Name);

Console.WriteLine(myTask.ToString());

output of anonymous type
MyTask must be implicitly typed, as we are not modeling the concept of a Task using a strongly typed class definition. Compiler will autogenerate a uniquely named class which is not visible from C#.  Also we have to specify the set of properties (using object initialization syntax) that model the data we are attempting to encapsulate. Once defined, these values can then be obtained using standard C# property invocation syntax.
compiler-generated class represent (ildasm.exe)
Each name/value pair defined using the object initialization syntax is mapped to an identically named read-only property and corresponding private read-only backing field. Good to know that Equals(), GetHashCode(), and ToString() is already overridden. ToString()  implementation builds a string from each name/value pair. GetHashCode() implementation computers a hash value using eash anonymous type's member variable as input to the System.Collections.Generic.EqualityComparer<T> (tow anonymous types have same hash value if they have same set of properties and values). Consider following example for Equal() method:

// Make 2 anonymous classes with identical name/value pairs.
var firstPet = new { Type = "Cat", Color = "Wight", Age = 55 };
var secondPet = new { Type = "Cat", Color = "Wight", Age = 55 };

// Equals()  --> test value type
if (firstPet.Equals(secondPet))
    Console.WriteLine("Same object!");
else
    Console.WriteLine("Different object!");

//  ==    --> test reference type
if (firstPet == secondPet)
    Console.WriteLine("Same object!");
else
    Console.WriteLine("Different object!");

//objects type
if (firstPet.GetType().Name == secondPet.GetType().Name)
    Console.WriteLine("Same type!");
else
    Console.WriteLine("Different types!");

Equal example output
Keep in mind Anonymous types cannot support events, custom methods, custom operators, or custom overrides. They are always implicitly sealed and created using default constructor.
Here is another example demonstrating nested anonymous types:

//Make an anonymous type that is composed of another
var myProduct = new
{
    ProductName = "Lumia 800",
    Manufacturer = new { Name = "Nokia", Contact = "link.nokia.com/support" },
    Price = 1650,
};

Pointer Types:
To bypass the CLR's memory-management and take matters into our own hand and .NET run-time has absolutely no clue of your intentions. This is helpful for some optimization purpose or calling methods of C-based .dll or COM server that demand pointer types as parameter. In order to inform the compiler you need to set the following flag: project's Properties-->Build tab --> Allow unsafe code.(below screen shot)
Setting the unsafe flag

After setting unsafe flag you are allowed to use unsafe syntax in your code like the following examples:

unsafe
{
    //work with pointer type unsafe block
}

//unsafe structure
public unsafe struct Node
{
    public int Value;
    public Node* Left;
    public Node* Right;
}

Methods (static or not-static) can marked as unsafe as well. In C# the *operator is applied to the underlying type only, not as a prefix to each pointer variable name.Consider following example:

//Wrong !!
int *p1, *p2;
//Right
int* p1,p2;

stackalloc:
In order to allocate memory directly from the call stack ( not subject to .NET garbage collection). Therefore the allocated memory is cleaned up as soon as the allocating method has returned. Let's go through an example:

unsafe static void UnsafeStacAlloc()
{
    char* p = stackalloc char[256];
    for(int k = 0 ; k < 256; k++)
        p[k] = (char) k;
}

fixed Keyword:
In order to set a pointer to a manged type and "pin" that variable during the execution of the code. In fact compiler do not allow you to set a pointer to a managed variable except in a fixed statement. In another word fixed keyword help you to lock a reference variable in memory, such that its address remains constant for the duration of the statement (or scope block). Here is an example:

class PointClass
{
    public int x;
    public int y;
}

PointClass pt = new PointClass();
pt.x = 5;
pt.y = 6;

fixed (int* p = &pt.x)
{
    //use int* variable!
}

sizeof Keyword:
Used to obtain the size in bytes of a value type (never a reference type). As sizeof will evaluate the number of bytes for any System.ValueType-derived entity, you can obtain the size of custom structures as well. Example:

unsafe
{
    Console.WriteLine("The size of short is {0}.", sizeof(Node));
}

Keep in mind when System.ValueType-derived doesn't have predefined size you must put it in unsafe context.

Reference: Pro C# 2010 and the .NET 4 Platform by Andrew Troelsen.

No comments:

Post a Comment