Partager via


LINQ Farm: Extension Methods and Scoping

There are a few scoping rules that you must keep in mind when using extensions methods. Problems with scoping and extensions methods are rare, but when you encounter them they are quite vexing.

An instance method will always be called before an extension method. The runtime looks first for an instance method, if it finds an instance method with the right name and signature, it executes it and never looks for your extension method. The following code illustrates this problem:

 using System;

namespace ConsoleApplication1
{
    public class MyClass
    {
        public void DoThis()
        {
            Console.WriteLine("MyClass.DoThis");
        }
    }


    public static class MyExtensions01
    {
        // Can never be called as if it were an instance method of MyClass.
        public static void DoThis(this MyClass myClass)
        {
            Console.WriteLine("MyExtensions01.DoThis");
        }
    }


    class Program
    {
        static void Main(string[] args)
        {
            MyClass myClass = new MyClass();
            myClass.DoThis();                // Calls MyClass.DoThis
            MyExtensions01.DoThis(myClass);  // Calls MyExtensions01.DoThis
        }
    }
}

MyExtensions01.DoThis is a valid extension method for MyClass. However, it will never be called because MyClass.DoThis always takes precedence over it unless you explicitely call it as a static method of MyExtensions01.

In cases where you have two extension methods with the same name, an extension method in the current namespace will win out over one in another namespace. Ambiguity will become an issue, however, when you try to call two extension methods with the same name and signature in the same namespace, or in two different namespaces both used by the current namespace. See Listing 5 for an example of this problem.

The following code will not compile because the compiler finds the call to DoThat ambiguous:

 using System;
using System.Collections.Generic;
using System.Linq;

namespace ExtensionScope
{
    public class MyClass
    {
        public void DoThis()
        {
            Console.WriteLine("Do this");
        }
    }
}

namespace Extensions01
{
    using ExtensionScope;

    public static class MyExtensions01
    {
        // Can never be called
        public static void DoThis(this MyClass myClass)
        {
            Console.WriteLine("Do this");
        }

        public static void DoThat(this MyClass myClass)
        {
            Console.WriteLine("Do bop");
        }
    }
}

namespace Extensions02
{
    using ExtensionScope;

    public static class MyExtensions02
    {
        // Can never be called
        public static void DoThis(this MyClass myClass)
        {
            Console.WriteLine("Do this");
        }

        public static void DoThat(this MyClass myClass)
        {
            Console.WriteLine("Do bang");
        }
    }
}

namespace ExtensionScope
{
    using Extensions01;
    using Extensions02;

    class Program
    {
        static void Main(string[] args)
        {
            MyClass m = new MyClass();
            m.DoThat();
        }
    }
}

This program throws a compile time error because the compiler does not know if you want to MyExtionsions01.DoThat() or MyExtension02.DoThat() . There are two ways to resolve this error:

  • You could remove the using directive for either Extensions01 or Extensions02. In this case, that would be a fine resolution, but if there were other methods or classes in both Extensions01 and Extensions02 that you wanted to use, then this could become a painful, or even unacceptable, choice.
  • You could explicitly state which method you want to call using standard static syntax: MyExtensions01.DoThat(m) .
  • You could move either MyExtensions02 or MyExtensions01 into the ExtensionScope namespace: 
 namespace ExtensionScope
{
    public static class MyExtensions02
    {
        public static void DoThat(this MyClass myClass)
        {
            Console.WriteLine("MyExtensions02.DoThat");
        }
    }
}

This latter solution works so long as you have access to the source.

It should be clear that some of the issues discussed here can lead to trouble if you are not careful. In particular, you don't want to end up in a situation where forcing someone to remove a namespace results in them losing access to important functionality, nor do you want to force them to choose between functionality they desire and using your extensions.

It can also be a serious nuisance if you muddy a namespace with what many developers might consider superfluous methods. If you added 50 extension methods to the C# string class, then developers who just want to access the base functionality of that object would always have to contend with your methods, particularly when using IntelliSense.

To avoid or at least mitigate the seriousness of these problems, you should always place your extension methods in unique namespace separated from the rest of your code so that you can easily include or exclude the extension methods from a program. Listings 10 and 11 illustrate this technique.

Place your extensions in a separate file, and give them a unique namespace:

 namespace MyCode
{
    public class MyCode
    {
        // Code omitted here
    }
   
}
 namespace MyCode.Extensions
{
    public static class SpecialString
    {
        private static string[] stateCodes = 
                {"AL","AK","AZ","AR","CA","CO","CT","DE","FL",
                 "GA","HI","ID","IL","IN","IA","KS","KY","LA",
                 "ME","MD","MA","MI","MN","MS","MO","MT","NE",
                 "NV","NH","NJ","NM","NY","NC","ND","OH","OK",
                 "OR","PA","RI","SC","SD","TN","TX","UT","VT",
                 "VA","WA","WV","WI","WY"};

        public static bool IsState01(this string source)
        {
            if (source == null) return false;
            source = source.ToUpper(); 
            foreach (var item in stateCodes)
            {
                if (source == item)
                {
                    return true;
                }
            }
            return false;
        }

        public static bool IsState02(this string source)
        {
            return (source == null) ? false : stateCodes.Contains(source.ToUpper());
        }
    }
}

In this code I show you two alternative ways to implement the IsState extension method. The second, which uses LINQ, is probably easier to maintain. You can access the extension methods in a namespace MyCode.Extensions like this:

 using System;
using MyCode;
using MyCode.Extensions;

namespace ConsoleApplication1
{
    class Program
    {            
        static void Main(string[] args)
        {
            MyCode myCode = new MyCode();
            // Use My Code here.
            string test = "WA";
            if (test.IsState02())
            {
                Console.WriteLine("{0} is a state", test);
            }
        }
    }
}

In this code your extension method is available and the code compiles. Comment out the third using statement and your extension method would not be available and the code would not compile. The developer would, however, still have access to the functionality found in the MyCode namespace. You could perhaps improve this technology by putting your extensions in their own assembly with its own namespace. You could then be sure that developers could choose to include or exclude the extra weight of your extension methods when they ship their code.

Though extension methods are particularly useful in LINQ, they are now a part of the language, and if used with caution, they can be useful. Placing them in their own namespace is a best practice that should help you get the most from this feature.

kick it on DotNetKicks.com

Comments

  • Anonymous
    June 27, 2008
    You've been kicked (a good thing) - Trackback from DotNetKicks.com

  • Anonymous
    June 27, 2008
    Uh... Is it just me, or is IsState not an extension method?

  • Anonymous
    June 28, 2008
    Thanks TraumaPony. I was experiementing with it and changed it back and forth, and forgot to change it back. Should be fixed now.

  • Anonymous
    June 28, 2008
    source.ToUpper().Equals(item)??? Oh, sure, that's much better than StringComparison.

  • Anonymous
    June 30, 2008
    The comment has been removed

  • Anonymous
    July 07, 2008
    Sir would u please send me the C# various queries to bring a command over LinQ best regards Syed

  • Anonymous
    July 07, 2008
    Jon, Thanks for the comment on using statements vs using directives. In my old Delphi days, using statements was correct, and some old habits die hard. In C#, there are using statements that are part of method bodies, and so we need a way to distinguish the two items. You are therefore right to point out that directive is the correct term. I've updated the text accordingly. I understand and sympathize with your desire to have a way to include a switch that allows you to specify whether or not to bring in the extension methods in a namespace. The language designers certainly heard comments like yours on this issue, but ultimately they opted not to use that kind of switch, as outlined above. Their decision has several consequences:

  1. It keeps the language as simple and clean as possible. Every new switch is something that new users need to master, and another detail which developers must track. There is always a good argument for adding any one switch. Problems can occur, however, if this process incrementally leads the designers to add hundreds of switches, making the language potentially unwieldy.
  2. Another potential problem could arise if a library designer opted to use the technique shown above in my post, but the user tried to use switches to turn extension methods on and off, or vice versa. Then you could end up with the switch being used in places where it was not needed, and left off where it was needed. All that could lead to confusion.
  3. The method the designers of the C# language chose ultimately puts the burden squarely on the shoulders of the designer of a library. A well designed library makes it easy for users of the library to include or exclude extension methods, as described above. It also gives developers of a library the option of forcing users to include the extension methods, if the library designer feels that his library can't be used without them. In saying all this, I don't mean to imply that your comments are off-base or ill-considered. This is a complicated issue, and your suggestion does have real merit. I believe, however, that ultimately the C# team made a good call on this one.
  • Charlie
  • Anonymous
    July 08, 2008
    Syed, One of the best ways to get up to speed on LINQ is to look at the SampleQueries project that ships with the C# samples that accompany Visual Studio 2008. In Visual Studio, choose Help | Samples from the menu, and read the instructions to download or locate the C# samples. Unzip the CSharpSamples.zip file, and locate the SampleQueries project in the LinqSamples directory. That project has over 100 examples of how to use LINQ with each of its major providers: LINQ to SQL, LINQ to XML, LINQ to Objects, and LINQ to DataSet. It ends up being some 500 different samples all rolled into one project.
  • Charlie
  • Anonymous
    July 23, 2008
    Charlie, I have a in c# form with the file form1.designer.cs and i would like to put a extension method in this file. When i do this and change the form to view designer i have a crash in my application and i can't see the designer of this form. Can´t i use extension methods in view designer ? Vinicius

  • Anonymous
    December 06, 2008
    Gathering all this is neat! I suppose appling will also get me into doing more Shortly

  • Anonymous
    August 31, 2009
    The comment has been removed