Click here to Skip to main content
15,885,896 members
Articles / Programming Languages / C#

I Love C# Extension Methods

Rate me:
Please Sign up or sign in to vote.
4.22/5 (10 votes)
21 Oct 2011CPOL3 min read 56.6K   358   30   8
Some useful C# Extension Methods
avoid this

Introduction

I found a nice way to avoid nested "if"s or nested property checks against null:

C#
var root = new Node(args);
if (root.Left != null && root.Left.Left != null) {
   // do something
}
var l = root.Left;
if (l != null) {
	var ll = l.Left;
	if (ll != null) {
		// do something
	}
}
// or with GetProperty
var ll=root.GetProperty(_ => _.Left).GetProperty(_ => _.Left);
if (ll != null){
	// do something
}

Background

Extension methods can extend any type in the .NET world. This is a sample from Microsoft's MSDN from here.

C#
public static class Sample {
	// from Extension Methods (C# Programming Guide
	public static int WordCount(this String str) {
		return str.Split(new char[] { ' ', '.', '?' },
			StringSplitOptions.RemoveEmptyEntries).Length;
	}
}

With this, you can call the extension method as if the type (string here) had implemented it.

C#
int x="1 2 3 4".WordCount();

This is only syntax sugar. You can also write:

C#
int x=Sample.WordCount("1 2 3 4");

It's a static method - and the compiler allows to write the call in both syntax-versions - that's all.
and...?
I love this sugar because I can check the parameter 'str':

C#
public static class Sample {
	public static int MyWordCount(this String str) {
		if (string.IsNullOrEmpty(str)) {
			return 0;
		}
		return str.Split(new char[] { ' ', '.', '?' },
			StringSplitOptions.RemoveEmptyEntries).Length;
	}
}

This a good thing since I can call:

C#
string s=null; int x=s.MyWordCount();

and everything works fine - no NullReferenceException.
Combine this with lambda expression and see there GetProperty is born.

Using the Extension Method GetProperty

In the following code, I use no additional classes, and the samples become a little bit silly, but I hope you see how GetProperty works.

C#
static void Main(string[] args) {

	// get the 9th parameters's length or 0 (= default(int))
	var usedefault = args.Skip(8).FirstOrDefault().GetProperty(_ => _.Length);
	System.Console.Out.WriteLine("1) usedefault
				: " + usedefault.ToString());

	// get the 9th paramters's length or -1
	var withdefaultValue = args.Skip(8).FirstOrDefault().GetProperty
				(_ => _.Length, -1);
	System.Console.Out.WriteLine("2) withdefaultValue
				: " + withdefaultValue.ToString());

	// get the 9th parameter in upper case or the %COMPUTERNAME%
	var withdefaultValueFunction1 = args.Skip(8).FirstOrDefault().GetProperty(
		_ => _.ToUpperInvariant(),
		() => System.Environment.MachineName);

	System.Console.Out.WriteLine("3) withdefaultValueFunction1
				: " + withdefaultValueFunction1.ToString());

	// get the 9th parameter's Length or a expensive computing 
         // (the length of the first file in the current directory)
	var withdefaultValueFunction2 = args.Skip(8).FirstOrDefault().GetProperty(
		_ => _.Length,
		() => (new System.IO.DirectoryInfo(@".")).GetFiles
				("*.*", System.IO.SearchOption.TopDirectoryOnly)
			.FirstOrDefault()
			.GetProperty(_ => _.Length));
	System.Console.Out.WriteLine("4) withdefaultValueFunction2 : " +
				withdefaultValueFunction2.ToString());
}

and the results are:

1) usedefault                : 0
2) withdefaultValue          : -1
3) withdefaultValueFunction1 : myhostname
4) withdefaultValueFunction2 : 10752

Okay - I think example 4 makes absolutely no sense ( except here;-). But if you call the program with enough parameters, the Length property will be used. When you invoke the program without parameters, the lambda expression is called. I don't use an if nor a ?: to check if the property can be accessed or rather the expression can be called.

The Implementation of GetProperty

The implementation is small - and if you really like generic programming - nice, if not ... I did this for you - you can just use it.

C#
public static TP GetProperty<tc>(this TC that, Func<tc> func)
	where TC : class {
	if (object.ReferenceEquals(that, null)) {
		return default(TP);
	}
	return func(that);
}
public static TP GetProperty<tc>(this TC that, Func<tc> func, TP defaultValue)
	where TC : class {
	if (object.ReferenceEquals(that, null)) {
		return defaultValue;
	}
	return func(that);
}
public static TP GetProperty<tc>(this TC that, Func<tc> func, Func<tp /> funcDefault)
	where TC : class {
	if (object.ReferenceEquals(that, null)) {
		return funcDefault();
	}
	return func(that);
}

Using the Extension Method GetProperty II

For the next sample, I create this simple (Tree-)Node class.

C#
public class Node {
	public Node(string[] args) {
		this.Name = "root";
		var node = this;
		foreach (var arg in args) {
			if ((arg.Length % 2) == 0) {
				node = node.Left = new Node(arg);
			} else {
				node = node.Right = new Node(arg);
			}
		}
	}
	public Node(string name) {
		this.Name = name;
	}
	public readonly string Name;
	public Node Left { get; set; }
	public Node Right { get; set; }
}
//
 static void Main(string[] args) {
	if (args.Length == 0) {
		args = new string[]
			{ "left", "right", "Left", "Right", "LEFT", "RIGHT" };
	}
	var root = new Node(args);
	// before GetProperty
	if (root.Left != null && root.Left.Left != null) {
		var ll1 =root.Left.Left;
		// ...
	}
	// or with GetProperty
	var ll2=root.GetProperty(_ => _.Left).GetProperty(_ => _.Left);
	if (ll2 != null){
		//...
	}
}

The "var ll2" will be null or not - but it doesn't raise an NullReferenceException.
My point is if you can easily write...

C#
if (root.Left != null && root.Left.Left.Left != null)
	{ var name =root.Left.Left.Left.Name;} 

...and you have forgotten one Left
- or -
you check for...

C#
root.Left.Left.Left != null 

...and use later

C#
var node=root.Left.Right.Left;.

If you're using GetProperty, you write the one expression once - and no copy & paste and modify / no try - debug - modify - try - debug - modify - will bring this one expression into two different expressions.

In the next samples, I'll hope I can show you how nice this can be: (btw _ underscore is a valid identifier).

C#
static void Main(string[] args) {
	if (args.Length == 0) {
		args = new string[]
			{ "left", "right", "Left", "Right", "LEFT", "RIGHT" };
	}
	var root = new Node(args);
	try {
		var lr = root.Left.Right;
		// root.Left can be null so root.Left.Right might fail 
		// (not with the default args)
		System.Console.Out.WriteLine("5) lr.Name = " + lr.Name);
	} catch (NullReferenceException) {
		System.Console.Out.WriteLine("5) lr NullReferenceException.");
	}
	try {
		var ll = root.Left.Left;
		// root.Left can be null so root.Left.Left might fail 
		// (with the default args it will)
		System.Console.Out.WriteLine("6) ll.Name = " + ll.Name);
	} catch (NullReferenceException) {
		System.Console.Out.WriteLine("6) ll NullReferenceException");
	}
	try {
		var lr = root.GetProperty(_ => _.Left).GetProperty(_ => _.Right);
		System.Console.Out.WriteLine("7) lr.Name = " + lr.Name);
		// lr can be null so lr.Name might fail (not with the default args)
	} catch (NullReferenceException) {
		System.Console.Out.WriteLine("7) lr NullReferenceException");
	}
	try {
		var ll = root.GetProperty(_ => _.Left).GetProperty(_ => _.Left);
		System.Console.Out.WriteLine("8) ll.Name = " + 
			ll.GetProperty(_ => _.Name, "ll is null"));
	} catch (NullReferenceException) {
		System.Console.Out.WriteLine("8) NullReferenceException");
	}
}

and the results are:

5) lr.Name = right
6) ll NullReferenceException
7) lr.Name = right
8) ll.Name = ll is null

The last sample (8) you never get an error here - even with other parameter.

Of course, you can create special Extension methods for Left and Right - I'll do this for a few properties I really often use and the objects may be null - I do this if the code becomes more readable - but I do this only for a small number of properties. GetProperty works for all.

C#
public static class NodeExtension {
	public static Node GoLeft(this Node that) {
		if (that == null) {
			return null;
		} else {
			return that.Left;
		}
	}
	public static Node GoRight(this Node that) {
		if (that == null) {
			return null;
		} else {
			return that.Right;
		}
	}
}
...
static void Main(string[] args) {
	...
	var lrlr = root.GoLeft().GoRight().GoLeft().GoRight();
	System.Console.Out.WriteLine("9) lrlr.Name = " +
		lrlr.GetProperty(_ => _.Name, "lrlr is null"));
}
...

It often makes no difference if the one property is null or the other property is null, the interesting object cannot be reached.
Often a method checks this, checks the other thing and after all, it just calls the method that really does something - or does nothing useful (or throws an exception) because of missing information.

Two things at last:

  • structs cannot be null. So GetProperty only makes sense with classes.
  • You have to add the using NamespaceOfExtension; in each file you want to use it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer Solvin
Germany Germany
I'm starting with C64 with Basic than continue on the Amiga 500 with Assembler, PC 286 Turbo Pacal, ... ,C++, Scheme, Oberon, Java, Perl, Python and now I only work with .Net mainly C# - What a cool language!
Currently i work at my company with Project Server, SharePoint, HTML, TypeScript, SQL, PowerShell and C#.

Comments and Discussions

 
GeneralMy vote of 1 Pin
behzad200022-May-13 21:32
behzad200022-May-13 21:32 
QuestionI wish they'd add this to C# Pin
Qwertie25-Oct-11 4:27
Qwertie25-Oct-11 4:27 
GeneralCode quality Pin
Alexander Chernosvitov23-Oct-11 0:16
Alexander Chernosvitov23-Oct-11 0:16 
Good extensions are welcomed. Some of your extensions are useful, but I doubt the usefulness of SetAs. The example you supplied is far-fetched (too artificial). I would rather use more simple code:
C#
foreach (FileSystemInfo fsi in (new DirectoryInfo(@"c:\")).GetFileSystemInfos())
  Console.WriteLine((fsi is FileInfo ? "File: " : "Dir: ") + fsi.Name);
.
GeneralRe: Code quality Pin
grimmuko23-Oct-11 18:05
grimmuko23-Oct-11 18:05 
GeneralMy vote of 1 Pin
ervegter21-Oct-11 9:59
ervegter21-Oct-11 9:59 
GeneralMy vote of 1 Pin
ashved21-Oct-11 5:48
ashved21-Oct-11 5:48 
GeneralRe: My vote of 1 Pin
grimmuko21-Oct-11 12:31
grimmuko21-Oct-11 12:31 
GeneralMy vote of 3 Pin
Member 781864021-Oct-11 1:00
Member 781864021-Oct-11 1:00 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.