Chapter 8 - Advanced C# language features

The content and code of this article is referenced from book Pro C#5.0 and the .NET 4.5 Framework by Apress. The intention of the writing is to review the konwledge and gain better understanding of the .net framework. 

1. Indexer method

As a developer, we are all familar with the process of accessing array using index opeator []. The .net framework provides the capability to design custom classes and structures that may be indexed just like a standard array, by defining indexer method.

An indexer is represented as a slightly modified c# property definition. 

 1    class PersonCollection : IEnumerable
 2     {
 3         private ArrayList arPeople = new ArrayList();
 4 
 5         //custom indexer for this class
 6         public Person this[int index]
 7         {
 8             get { return (Person) arPeople[index]; }
 9             set { arPeople.Insert(index, value);}
10         }
11 
12         public IEnumerator GetEnumerator()
13         {
14             throw new NotImplementedException();
15         }
16     }

Apart from using this keyword, the indexer looks like just like any other c# property declaration.  In addition, do remember that generic types give you this very functionality out of the box. 

1      private static void UserGenericList()
2         {
3             List<Person> myPeople = new List<Person>();
4             myPeople.Add(new Person{FirstName = "person1", LastName = "test", Age = 20});
5             myPeople.Add(new Person { FirstName = "person2", LastName = "test", Age = 22 });
6 
7             //using indexer method
8             myPeople[0] = new Person() {FirstName = "Jason", LastName = "test", Age = 32};
9         }

1.1 Indexing data using string value

In the previous example, we defined an indexer method that allowed the caller to identify subitems using a numerical value. However, this is not a requirement for an indexer method, we can use string value as well. 

 1     class PersonCollection : IEnumerable
 2     {
 3         private Dictionary<string, Person> listPeople = new Dictionary<string, Person>(); 
 4 
 5 
 6         //custom indexer for this class
 7         public Person this[string name]
 8         {
 9             get { return (Person) listPeople[name]; }
10             set { listPeople[name] = value; }
11         }
12 
13         public IEnumerator GetEnumerator()
14         {
15             return listPeople.GetEnumerator();
16         }
17     }

Again, if you were to use the generic Dictionary<TKey, TValue> type directly, you'd gain the indexer method functionality out of the box. 

1.2 Overloading indexer method

Understand that indexer methods may be overloaded on a single class or structure. 

1.3 Indexers with multiple dimensions

You may also create an indexer method that takes multiple parameters. 

1         public int this[int row, int column]
2         {
3             get { return myarray[row, column]; }
4             set { myarray[row, column] = value; }
5         }

1.4 Indexer on interface type

Indexers can be defined on a given .net interface type to allow supporting types to provide a custom implementation. 

    interface IStringContainer
    {
        string this[int index] { get; set;}
    }

Any class or structure that implements this interface must now support a read/write indexer that manipulates subitems using a numerical value. 

2. Operator overloading

In c#, it gives you the capability to build custom classes and structures that also respond uniquely to the same set of basic tokens.

2.1 overloading binary operator

c# provides the operator keyword, which you can use only in conjunction with the static keyword.  

 

 1     public class Point
 2     {
 3         public int X { get; set; }
 4         public int Y { get; set; }
 5 
 6         //overloaded operator + 
 7         public static Point operator + (Point p1, Point p2)
 8         {
 9             return new Point(p1.X + p2.X, p1.Y + p2.Y);
10         }
11 
12         public static Point operator -(Point p1, Point p2)
13         {
14             return new Point(p1.X - p2.X, p1.Y - p2.Y);
15         }
16 
17         public Point(int xPos, int yPos)
18         {
19             X = xPos;
20             Y = xPos;
21         }
22 
23     }

When you are overloading a binary operator, you are not required to pass in two parameters of the same type. 

1 //overloaded operator with different types of parameters 
2         public static Point operator + (Point p1, int change)
3         {
4             return new Point(p1.X + change, p1.Y + change);
5         }

2.2 overloading Unary operator

C# also allows you to overload various unary operators, such as ++ and --. When you overload a unary operator, you also must use the static keyword with the operator keywrod; In this case, you simple pass in a single parameter that is the same type as the defining class/structure. 

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

2.3 Overloading equality operators

System.object.Equals() can be overridden to perform value-based (rather that reference-based) comparisons between reference types. 

 1       //overload the == and !=
 2         public static bool operator ==(Point p1, Point p2)
 3         {
 4             return p1.Equals(p2);
 5         }
 6 
 7         public static bool operator !=(Point p1, Point p2)
 8         {
 9             return !p1.Equals(p2);
10         }

keep in mind that, C# demands that if you override the == operator, you must also override the != opertaor. 

2.4 Overloading comparison operator

In previous chapter, we learned how to implement the IComparable interface in order to compare the relationship between two objects. You can also overload the comparison operator for the same class. 

        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);
        }

 

3. Custom type conversion

3.1 Conversion recap

In terms of the intrinsic numerical types (int, float), an explicit conversion is required when you attempt to store a larger value into a smaller container. Implicit conversion happens automatically when you attempt to place a smaller type in a destination type that will not result in loss of data. 

        static void Main(string[] args)
        {
            int a = 123;
            long b = a; //implicit
            int c = (int) b; //explicit
        }

c# also allows you to cast up and down the class hierarchy. A derived can always be implicitly cast to a base type. However, if you want o store a base class type in a derived variable, you must perform an explicit cast. 

What if you have two class types with no common parent that requires conversion? 

3.2 Custom conversion routines

C# provides two keywords, explicit and implicit, that you can use to control how your types respond during an attempted conversion. 

 1     public class Square
 2     {
 3         public int Width { get; set; }
 4 
 5         public static explicit operator Square(int sideWidth)
 6         {
 7             return new Square{Width = sideWidth};
 8         }
 9 
10         public static explicit operator int(Square s)
11         {
12             return s.Width;
13         }
14     }
 1     public class Rectangle
 2     {
 3         public int Width { get; set; }
 4         public int Height { get; set; }
 5 
 6         public static implicit operator Rectangle(Square s)
 7         {
 8             return new Rectangle{Height = s.Width, Width = 2 * s.Width};
 9         }
10     }

 

4. Extension Methods

.net 3.5 introduced the conecpt of extension methods, which allows you to add new methods or properties to a class or structure, without modifying the original type in any direct manner.

4.1 Define extension method

When you define extension method, the first restriction is that they must be defined within a static class, and, therefore, each extension method must be declared with the static keyword. Secondly, all extension methods are marked as such by using this keyword as a modifier on the first parameter of the method. 

    public static class MyExtensions
    {
        public static void DisplayDefiningAssembly(this object obj)
        {
            Console.WriteLine ("{0} lives here :=> {1}\n", obj.GetType ().Name, Assembly.GetAssembly (obj.GetType ()).GetName ().Name);
        }
    }

Invoke the extension method.

        public static void Main (string[] args)
        {
            int myInt = 2324;
            MyExtensions.DisplayDefiningAssembly (myInt); //calling extension method
        }

4.2 importing extension method

If the namespace is different from the namespace using the extension methods, you will need to make use of the expected C# using keyword. It's common practise to not only isolate extension methods into a dedicated .net namespace, but into a dedicated class library. 

4.3 Extending types implementing specific interfaces

It is possible to define an extension method that can only extend a class or structure that implements the correct interface. 

        public static void PrintData(this System.Collections.IEnumerable iterator)
        {
            foreach (var item in iterator) {
                Console.WriteLine (item);
            }
        }

And now, the extension can be invoked by any class that implements IEnumerable. 

       public static void Main (string[] args)
        {
            string[] data = { "test1", "test2", "test3" }; //array does implement IEnumerable
            MyExtensions.PrintData (data);
        }

 

5. Working with anonymous type

As OO developer, we all know the benefits of defining classes. However, there are other times when you would like to define a class simply to model a set of data without any methods or events.

5.1 Defining anonymous type

When you define anonymous type, you do so by making use of the var keyword in conjunction with object initialization syntax.

        static void BuildType()
        {
            var person = new {Name = "jason", Age = 25};  //anonymous type

            Console.WriteLine ("Person name is {0}", person.Name);
        }

 

5.2 Internal representation of anonymous type

All anonymous type are automatically derived from System.Object, and therefore, support each of the members provided by this base class. 

5.3 Anonymous types containing anonymous type

        static void BuildType()
        {
            var purchaseItem = new {
                TimeBought = DateTime.Now,
                ItemBought = new {Make = "make1", Color= "Red"},
                Price = 12
            };
        }

 

posted @ 2015-04-07 20:44  tim_bo  阅读(209)  评论(0编辑  收藏  举报