This already is the third part in a blogpost series where we explore lesser known C# language features. You can find part I here, and part II here. We already discussed several keywords and operators. We didn't receive a lot of feedback on this series. We would really appreciate any feedback! With this third post, we will continue on our quest to explore the lesser known features of C#

 

Extension methods

 

Extension methods were introduced in .NET 3.5 (and C# 3.0). They are an exceptional type of method. They are static methods, but are called if they where instance methods. You can use extension methods to 'extent' methods to an existing class. This means you can add methods to an already existing type, that you don't even have the sourcecode or internals for.

 

The big advantages of using Extension methods:

  • Reusability. Declare the method once, and it will be available troughout the application.
  • Extension methods extend methods: you can add methods to existing classes. This allows you to use the Object Oriented paradigm better
  • Portability. You can create your own library with extension methods you can use troughout different applications.

LINQ heavily relies on extension methods. You can recognize extension methods in Visual Studio by the downward arrow if you use Intellisense. You will also notice the '(extension)' prefix before the documentation.

 

6215.extensionmethods_5F00_intellisense.png

 

You can create your own extension methods by creating a static class. Inside this static class you declare static methods. The first parameter of these static methods should be of the type that you want to extent. This parameter should be preceded by the 'this' modifier.

 

In this example, we are extending the 'PIPoint' class with an extension method called 'GetAttribute', which takes an attribute name, and returns the attribute value.

 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using PISDK;

namespace PIExtensions
{
    public static class PIExtensions
    {
        public static string GetAttribute(this PIPoint point, string name)
        {
            return point.PointAttributes[name].Value.ToString();
        }
    }
}

 

 

We can use this extension method by bringing the 'PIExtensions' namespace into scope, and calling 'GetAttribute' on an object of type 'PIPoint'.

 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using PIExtensions;

namespace ExtensionMethods
{
    class Program
    {
        static void Main(string[] args)
        {
            var server = new PISDK.PISDK().Servers["hans-ottosrv"];
            var point = server.PIPoints["sinusoid"];

            var span = point.GetAttribute("span");

            Console.WriteLine(span);
            Console.ReadLine();
        }
    }
}

 

 

This way, no mather where  you are in your application: if you have a 'using PIExtensions;' directive, you can use the PIExtensions extension methods.

 

A more 'real live' example would be an extension method to copy a pipoint:

 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using PISDK;

namespace PIExtensions
{
    public static class PIExtensions
    {
        static string[] ReadOnlyAttributes = new string[] 
        {  
            "changedate",
            "changer",  
            "creationdate",
            "creator",    
            "pointid",   
            "pointnumber", 
            "pointtype",    
            "ptclassid",   
            "ptclassrev", 
            "recno" 
        };

        public static PISDK.PIPoint CopyPoint(this PISDK.PIPoint sourcePoint, string newName)
        {
            return CopyPoint(sourcePoint, newName, sourcePoint.Server);
        }

        public static PISDK.PIPoint CopyPoint(this PISDK.PIPoint sourcePoint, string newName, PISDK.Server destServer) 
         {      
             var sourceAttribs = sourcePoint.PointAttributes.GetAttributes();   
             var newAttribs = new PISDKCommon.NamedValues();            
             var ptClass = sourcePoint.PointClass.Name;             
             var ptType = sourcePoint.PointType;             
             foreach (PISDKCommon.NamedValue nv in sourceAttribs)   
             {               
                 if (!ReadOnlyAttributes.Contains(nv.Name.ToLower()))   
                 newAttribs.Add(nv.Name, nv.Value);        
             }          
             return destServer.PIPoints.Add(newName, ptClass, ptType, newAttribs);  
         }   
     
    }
}

 This is also an example of overloading your extension methods. In this case we have an CopyPoint method that accepts only a new tagname, and we have a CopyPoint method that accepts a new tagname and a new server. You can use these extension methods like so:

 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using PIExtensions;

namespace ExtensionMethods
{
    class Program
    {
        static void Main(string[] args)
        {
            var sdk = new PISDK.PISDK();
            var server = sdk.Servers["hans-ottosrv"];
            var otherServer = sdk.Servers.DefaultServer;
            var point = server.PIPoints["sinusoid"];

            //copy point to same server
            point.CopyPoint("sinusoid-new-1");

            //copy point to different server
            point.CopyPoint("sinusoid-new-otherserver", otherServer);

            Console.ReadLine();
        }
    }
}

 

 

 A few tips and tricks when dealing with Extension methods:

  • You cannot 'replace' or 'override' existing methods. If your extension method has the same name as an already existing method, your extension method will never get called
  • You can overload new extension methods
  • Extension methods are brought into scope on the namespace level. If you have multiple 'extension classes' defined in the same namespace, they will all be in scope.
  • If you have certain actions that you find cumbersome, or that you need in multiple applications: extension methods are a great way to have in a seperate 'helper' library, that you can reference into your new applications. This saves you a lot of time and effort.

 And, to conclude this article, some small tips and tricks about strings!

 

String checking and comparison

 

Since .NET 4.0, the String class has a 'NullOrWhiteSpace' method. There always was an 'NullOrEmpty' method, but that didn't account for extra whitespace. In order to be secure, you had to use the 'Trim()' method. This will fail if the string is indeed null, and throw an exception

 

Old way (bad):

 
            var checkString = " ";
            if (string.IsNullOrEmpty(checkString.Trim()))
                //do something

 

 

Using 'NullOrWhiteSpace' (good):

 
      var checkString = " ";
            if (string.IsNullOrWhiteSpace(checkString))
                //do something

 

 

When dealing with string comparison, do you do it like this?

 
       var string1 = "HeLLo WoRlD";

            var string2 = "hellO wOrlD";

            if (string1.ToUpper() == string2.ToUpper())
                //they are the same!
            else
                //they are not the same!

 

 

A far better way is using string.Equals(). This can account for cultural and case comparison.

 
  var string1 = "HeLLo WoRlD";
            var string2 = "hellO wOrlD";
            if (string1.Equals(string2, StringComparison.CurrentCultureIgnoreCase)
                //they are the same!
            else
                //they are not the same!

 

 

This will account for the culturesettings of the string, and is best practice when dealing with string comparison!

 

Further reads

 

 Extension Methods (MSDN)

 

Database of extension methods (Extensionmethod.net)

 

How to implement and call a custom Extension Method (MSDN)

 

String.IsNullOrWhiteSpace (MSDN)

 

String.Equals (MSDN)

 

Previous articles in this series

 

Exploring lesser known C# Language Features Part I

 

Exploring lesser known C# Language Features Part II