Extension methods are static methods that can be invoked using the instance method syntax. In effect, extension methods make it possible for us to extend existing types and constructed types with additional methods.
For example, we can define an extension method as follows:
public static class MyExtensions { public static bool IsCandy(this Product p) { if (p.ProductName.IndexOf("candy") >= 0) return true; else return false; } }
In this example,
the static method IsCandy
takes a this
parameter of the Product
type and searches for the string candy
inside the product name. If it finds a match, it assumes this
is a candy
product and returns true
. Otherwise, it returns false
, meaning this
is not a candy
product.
Because all extension methods must be defined in top-level static classes, here to simplify the example, we put this class inside the same namespace as our main test application TestLINQFeaturesApp
, and defined this class on the same level as the Program
class so that it is a top-level class. Now, in the program, we can call this extension method as follows:
if (product.IsCandy()) Console.WriteLine("yes, it is a candy"); else Console.WriteLine("no, it is not a candy");
It looks as if IsCandy
is a real instance method of the Product
class. Actually, it is a real method of the Product
class, but it is not defined inside the Product
class. Instead, it is defined in another static class to extend the functionality of the Product
class. This is why it is called an extension method.
Not only does it look like a real instance method, but this new extension method actually pops up when a dot is typed by using IntelliSense on the product
variable. The following screenshot shows the possible options when using IntelliSense on the product variable within Visual Studio:
Under the hood in Visual Studio, when a method call on an instance is being compiled, the compiler first checks to see if there is an instance method in the class for this method. If there is no matching instance method, it looks for an imported static class or any static class within the same namespace. It also searches for an extension method with the first parameter that is the same as the instance type (or is a super type of the instance type). If it finds a match, the compiler will call that extension method. This means that instance methods take precedence over extension methods, and extension methods that are imported in inner namespace declarations take precedence over extension methods that are imported in outer namespaces.
In our example, when product.IsCandy()
is being compiled, the compiler first checks the Product
class and doesn't find a method named IsCandy
. It then searches the static class MyExtensions
and finds an extension method with the name IsCandy
, and due to that it has a first parameter of the type Product
.
At compile time, the compiler actually changes the product.IsCandy()
call to this call:
MyExtensions.IsCandy(product)
Surprisingly, extension methods can be defined for sealed classes. As we all know a sealed class, by definition, is a class that cannot be modified in any way, for example, being inherited or overridden. However with extension methods, we can add as much functionality to a sealed class as we like. This gives us an opportunity to improve a class that we don't have ownership of. In our example, you can change the Product
class to be sealed and it still runs without any problem. This gives us great flexibility to extend system types because many of the system types are sealed.
On the other hand, extension methods are less discoverable and are harder to maintain, so they should be used with great caution. If your requirements can be achieved with an instance method, you should not define an extension method to do the same work.
Not surprisingly, this feature is again a Visual Studio compiler feature and the compiled assembly is a valid .NET assembly.
Extension
methods are the basis of LINQ. We will discuss the various LINQ extension methods defined by .NET Framework in the System.Linq
namespace later (the actual LINQ extension methods are defined in the System.Core
assembly, which is added to every project by default).
Now the Program.cs
file should be as follows:
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace TestLINQFeaturesApp { class Program { static void Main(string[] args) { // valid var statements var x = "1"; var n = 0; string s = "string"; var s2 = s; s2 = null; string s3 = null; var s4 = s3; /* string x = "1"; int n = 0; string s2 = s; string s4 = s3; */ // invalid var statements /* var v; var nu = null; var v2 = "12"; v2 = 3; */ // old way to create and initialize an object /* Product p = new product(1, "first candy", 100.0m); Product p = new Product(); p.ProductID = 1; p.ProductName = "first candy"; p.UnitPrice=100.0m; */ //object initializer var product = new Product { ProductID = 1, ProductName = "first candy", UnitPrice = (decimal)100.0m }; var arr = new[] { 1, 10, 20, 30 }; // collection initializer var products = new List<Product> { new Product { ProductID = 1, ProductName = "first candy", UnitPrice = (decimal)10.0m }, new Product { ProductID = 2, ProductName = "second candy", UnitPrice = (decimal)35.0m }, new Product { ProductID = 3, ProductName = "first vegetable", UnitPrice = (decimal)6.0m }, new Product { ProductID = 4, ProductName = "second vegetable", UnitPrice = (decimal)15.0m }, new Product { ProductID = 5, ProductName = "third product", UnitPrice = (decimal)55.0m } }; // anonymous types var a = new { Name = "name1", Address = "address1" }; var b = new { Name = "name2", Address = "address2" }; b = a; /* class __Anonymous1 { private string name; private string address; public string Name { get{ return name; } set { name=value } } public string Address { get{ return address; } set{ address=value; } } } */ // extension methods //if(MyExtensions.IsCandy(product)) if (product.IsCandy()) Console.WriteLine("yes, it is a candy"); else Console.WriteLine("no, it is not a candy"); } } public sealed class Product { public int ProductID { get; set; } public string ProductName { get; set; } public decimal UnitPrice { get; set; } } public static class MyExtensions { public static bool IsCandy(this Product p) { if (p.ProductName.IndexOf("candy") >= 0) return true; else return false; } } }
So far
in Program.cs
, we have carried out the following:
var
type variablesProduct
IsCandy
with a this
parameter of the Product
type to it, making this method an extension methodcandy
product and printed out a message according to its nameIf you run the program, the output will look like the one shown in the following screenshot: