NET 3.5 introduced the concept of extension methods, which allow you add new methods or properties to a class or structures, without modifying the original type in any direct manner.
Using extension methods, you can modify types without subclassing and without modifying the type directly- The catch is that the new functionality is offered to a type only if the extension methods have been referenced for use in your current project.
Defining Extension Methods
When you define extension methods, the restriction is that they must be defined within a static class; therefore, each extension method must be declared with the "static" keyword.
The second point is that all extension methods are marked as such by using the "this" keyword as a modifier on the first (and only the first) parameter of the method in question. The "this qualified" parameter represents the item being extended.
static class MyExtension
{
// This method allows any object to display the assembly it is defined on.
public static void DisplayDefiningAssembly(this object obj)
{
Console.WriteLine("Object lives here : " + obj.GetType().Name + " " + Assembly.GetAssembly(obj.GetType()).GetName().Name);
}
// This method allows any integer to reverse its digits for ex:
public static int ReverseDigits(this int i)
{
char[] digits = i.ToString().ToCharArray();
Array.Reverse(digits);
string newDigits = new string(digits);
return int.Parse(newDigits);
}Note: Understand that a given extension method can have multiple parameters, but only the first parameter can be qualified this. The additional parameters would be treated as normal incoming parameters for use by the method.
Invoking Extension Methods
int myInt = 1235678;
myInt.DisplayDefiningAssembly();
// So has the DataSet
DataSet d = new DataSet();
d.DisplayDefiningAssembly();
Console.WriteLine(myInt);
Console.WriteLine(myInt.ReverseDigits());Importing Extension Methods
When you define a class containing extension methods, it will no doubt be defined within a namespace. If this namespace is different from the namespace using the extension methods, you will need to make use of the expected C# "using" keyword. When you do, your code file has access to all extension methods for the type being extended. This is important to remember because if you do not explicitly import the correct namespace, the extension methods are not available for that C# code file.
namespace MyExtensionMethods;
static class MyExtension
{
// This method allows any object to display the assembly it is defined on.
public static void DisplayDefiningAssembly(this object obj)
{
Console.WriteLine("Object lives here : " + obj.GetType().Name + " " + Assembly.GetAssembly(obj.GetType()).GetName().Name);
}
// This method allows any integer to reverse its digits for ex:
public static int ReverseDigits(this int i)
{
char[] digits = i.ToString().ToCharArray();
Array.Reverse(digits);
string newDigits = new string(digits);
return int.Parse(newDigits);
}
}To use the extension methods in the class, you need to explicitly import the MyExtensionMethods namespace.
Extending Types Implementing Specific Interfaces
It's also possible to define an extension method that can only extend a class or structure that implements the correct interface.
static class AnnoyingExtensions
{
public static void PrintDataAndbeep(this System.Collections.IEnumerable iterator)
{
foreach(var item in iterator)
{
Console.WriteLine(item);
Console.Beep();
}
}
}
string[] data = { "Wow", "this", "is" };
data.PrintDataAndbeep();
// List<T> implements IEnumerable
List<int> myInts = new List<int> { 10, 15, 20 };
myInts.PrintDataAndbeep();Extension Method GetEnumerator Support
Prior to C# 9.0, to use foreach on a class, the GetEnumerator() method had to be defined on that class directly. With C# 9.0, the foreach method will examine extension methods on the class and if a GetEnumerator() method is found, will use that method to get the IEnumerator for that class.
class Car
{
public int CurrentSpeed { get; set; }
public string PetName { get; set; }
public Car()
{
}
public Car(string name, int speed)
{
CurrentSpeed = speed;
PetName = name;
}
}
class Garage
{
public Car[] CarsInGarage { get; set; }
public Garage()
{
CarsInGarage = new Car[2];
CarsInGarage[0] = new Car("Rust", 80);
CarsInGarage[1] = new Car("Clunker", 55);
}
}The GetEnumerator() method is added through the GarageExtensions class;
static class GarageExtensions
{
public static IEnumerator GetEnumerator(this Garage g)
{
return g.CarsInGarage.GetEnumerator();
}
}
Garage g = new Garage();
foreach(Car c in g)
{
Console.WriteLine(c.PetName+" "+c.CurrentSpeed);
}Understanding Anonymous Types
There are other times when you want to define a class simply to model a set of encapsulated data points without any associated methods, events, or other specialized functionality.
Defining an Anonymous Type
When you define an anonymous type, you do so by using the var keyword in conjunction with object initialization syntax. You must use the var keyword because the compiler will automatically generate a new class definition at compile time (and you never see the name of this class in your C# code). The initialization syntax is used to tell the compiler to create private backing fields and (read-only) properties for the newly created type.
static void BuildAnonymousType(string make,string color,int currSp)
{
// Build anonymous type using incoming args.
var car = new { Make = make, Color = color, Speed = currSp };
Console.WriteLine(car.Color+" "+car.Make+" "+car.Speed);
// Anonymous types have custom implementations of each virtual method of System.Object.
Console.WriteLine(car.ToString());
}Note that an anonymous type can also be created inline in addition to wrapping the code in a function.
// Make an anonymous type representing a car.
var myCar = new { Color = "Bright", Make = "Skoda", CurrentSpeed = 55 };
Console.WriteLine(myCar.Color);
// Now call our helper method to build anonymous type via args.
BuildAnonymousType("BMW", "Black", 90);The Internal Representation of Anonymous Types
All anonymous types are automatically derived from System.Object, and, therefore support each of the members provided by this base class.
static void ReflectOverAnonymousType(object obj)
{
Console.WriteLine("Obj is an instance of "+obj.GetType().Name);
Console.WriteLine("Base class of "+obj.GetType().Name+" "+obj.GetType().BaseType);
Console.WriteLine(obj.ToString());
Console.WriteLine(obj.GetHashCode());
}The Semantics of Equality for Anonymous Types
static void EqualityTest()
{
// Make 2 anonymous classes with identical name/value pairs.
var firstCar = new { Color = "Bright", Make = "Saab", CurrentSpeed = 55 };
var secondCar = new { Color = "Bright", Make = "Saab", CurrentSpeed = 55 };
Console.WriteLine(firstCar.Equals(secondCar)); // true
Console.WriteLine(firstCar == secondCar);// false
Console.WriteLine(firstCar.GetType().Name == secondCar.GetType().Name); // true
}The compiler will generate a new class definition only when an anonymous type contains unique names of the anonymous type. Thus, if you declare identical anonymous types(again, meaning the same names) within the same assembly, the compiler generates only a single anonymous type definition.
Anonymous Types Containing Anonyous Types
It's possible to create an anonymous type that is composed of other anonymous types.
var purchaseItems = new
{
TimeBought = DateTime.Now,
ItemBought = new { Color = "Red", Make = "Saab", CurrentSpeed = 55 },
Price = 34.000
};
ReflectOverAnonymousType(purchaseItems);- Anonymous types always extend System.Object
- Anonymous types cannot support events, custom methods, custom operators, or custom overrides.
- Anonymous types are always implicitly sealed.
- Anonymous types are always created using the default constructor.