Tech Off Thread

18 posts

Interface + Extension Methods -- any improvement on abstract classes?

Back to Forum: Tech Off
  • Dr Herbie

    OK, I've been going through some of the newer C# features and I've ended up with an interface and a genericised extension method (with type inference) on the interface.



    public interface IMyInterface
    {
    int SomeProperty {get; set;}
    void DoSomething(int);
    }

    public static class InterfaceExtender
    {
    public static T ExtensionMethod<T>(this T implementation, int i) where T : IMyInterface
    {
    implementation.DoSomething(i + implemenatation.SomeProperty);
    return implementation
    }
    }




    So now in code you can do


    IMyInterface item = new InterfaceImplementation();
    item.ExtensionMethod().SomeProperty = 5;


    (note, I'm using a flow syntax in the extension property).


    So the question:  are there any benefits to this over just using an abstract class with the extension method built-in?


    Herbie

  • Anthony​Johnston

    You can scope your extensions in our out eg

    namespace Sandbox.ExtensionMethods
    {
        class Class1
        {
        }
    }

    namespace Sandbox.ExtensionMethods.ns2
    {
        static class Extension
        {
            public static void Extend2(this Class1 item)
            {
            }
        }
    }

    namespace Sandbox.ExtensionMethods.ns1
    {
        static class Extension
        {
            public static void Extend1(this Class1 item)
            {
            }
        }
    }

    now putting a using for the namespace to the corresponding extension it will be scoped in

    eg

    using Sandbox.ExtensionMethods.ns2;

                Class1 item = new Class1();
                item.Extend2();

    Or


    using Sandbox.ExtensionMethods.ns1;

                Class1 item = new Class1();
                item.Extend1();

    not sure where you'd use it tho, Smiley Ant

  • Anthony​Johnston

    AnthonyJohnston said:
    You can scope your extensions in our out eg

    namespace Sandbox.ExtensionMethods
    {
        class Class1
        {
        }
    }

    namespace Sandbox.ExtensionMethods.ns2
    {
        static class Extension
        {
            public static void Extend2(this Class1 item)
            {
            }
        }
    }

    namespace Sandbox.ExtensionMethods.ns1
    {
        static class Extension
        {
            public static void Extend1(this Class1 item)
            {
            }
        }
    }

    now putting a using for the namespace to the corresponding extension it will be scoped in

    eg

    using Sandbox.ExtensionMethods.ns2;

                Class1 item = new Class1();
                item.Extend2();

    Or


    using Sandbox.ExtensionMethods.ns1;

                Class1 item = new Class1();
                item.Extend1();

    not sure where you'd use it tho, Smiley Ant
    Sorry,I completely mis-read your question - will be more careful in future,
    Ant

  • TommyCarlier

    I think interfaces are great for defining a "looks like" or "can do" relationship. An abstract base class is great for defining a "is a kind of" relationship. A class can only be derived from 1 base class but can implement multiple interfaces.

  • stevo_

    http://channel9.msdn.com/forums/TechOff/258761-Wow-extension-methods--interfaces/ you all said I was crazy! (actually you all ignored me Sad)...

    But I use this pattern a lot, I think its great.. also using extension methods are interesting because you can obviously still call the methods even on null objects..

  • Dr Herbie

    stevo_ said:
    http://channel9.msdn.com/forums/TechOff/258761-Wow-extension-methods--interfaces/ you all said I was crazy! (actually you all ignored me Sad)...

    But I use this pattern a lot, I think its great.. also using extension methods are interesting because you can obviously still call the methods even on null objects..
    Calling on null object ... hmm, I must add catches for that ...

    I'm using the extensions for flow syntax methods to match the property setters supported by the interface (because I want to support both), so the extension method ALWAYS calls the instance it's called on. I'm using it mainly for dependency injection:


    IMyInterface instance = new Implementation
    .WithPreProcessor(new ConcretePreprocessor())
    .WithPostProcessor(new ConcretePostProcessor());


    So what were you using them for, stevo_ ?

    Herbie

  • stevo_

    Dr Herbie said:
    stevo_ said:
    *snip*
    Calling on null object ... hmm, I must add catches for that ...

    I'm using the extensions for flow syntax methods to match the property setters supported by the interface (because I want to support both), so the extension method ALWAYS calls the instance it's called on. I'm using it mainly for dependency injection:


    IMyInterface instance = new Implementation
    .WithPreProcessor(new ConcretePreprocessor())
    .WithPostProcessor(new ConcretePostProcessor());


    So what were you using them for, stevo_ ?

    Herbie
    Various things, when designing new classes now I tend to cull the 'ugh' properties from interfaces and then let extensions handle them (although I wish they had extension properties as well).. but theres interesting things where you can do really clean systems for attaching values to objects, similar to how the attached properties work by the property value get and set really being routed to the static part of the dependent class.

    Mostly I just like how they let you avoid stressing interfaces with senseless 'overloads', you can write them as extension methods instead.. I know in the end these are just calls to helper classes that we could of done before, but the syntax for how they work is so much cleaner I'm much happier using them..

    Interesting thing I've seen the null thing used for - lazy patterns.

    Edit: The coolest lazy pattern I've seen is in the TPL though, they have a lazyinit class thats a struct (and so implicitely must construct), I thought that was clever..

  • wkempf

    Dr Herbie said:
    stevo_ said:
    *snip*
    Calling on null object ... hmm, I must add catches for that ...

    I'm using the extensions for flow syntax methods to match the property setters supported by the interface (because I want to support both), so the extension method ALWAYS calls the instance it's called on. I'm using it mainly for dependency injection:


    IMyInterface instance = new Implementation
    .WithPreProcessor(new ConcretePreprocessor())
    .WithPostProcessor(new ConcretePostProcessor());


    So what were you using them for, stevo_ ?

    Herbie
    A good example is providing convenience "overloads".  For example, there's talk about providing a build in service locator interface to the BCL.  One proposal was:
    public interface IServiceLocator
    {
       object GetInstance(Type type);
       T GetInstance<T>();
       object GetInstance(Type type, string name);
       T GetInstance<T>(string name);
       object[] GetAllInstances(Type type);
       T[] GetAllInstances<T>();
    }

    That's a lot for a concrete type to define, though, when half the methods will always just defer implementation to the other methods.  Better to use extension methods, here.
    public interface IServiceLocator
    {
       object GetInstance(Type type);
       object GetInstance(Type type, string name);
       object[] GetAllIntances(Type type);
    }
    
    
    public static class ServiceLocatorExtensions
    {
       public static T GetInstance<T>(this IServiceLocator self)
       {
          return (T)self.GetInstance(typeof(T));
       }
       public static T GetInstance<T>(this IServiceLocator self, string name)
       {
          return (T)self.GetInstance(typeof(T), name);
       }
       public static T[] GetAllInstances<T>()
       {
          return self.GetAllInstances(typeof(T)).Cast<T>().ToArray();
       }
    }

    Wow, Chrome doesn't post the code tags very well.

  • stevo_

    wkempf said:
    Dr Herbie said:
    *snip*
    A good example is providing convenience "overloads".  For example, there's talk about providing a build in service locator interface to the BCL.  One proposal was:
    public interface IServiceLocator
    {
       object GetInstance(Type type);
       T GetInstance<T>();
       object GetInstance(Type type, string name);
       T GetInstance<T>(string name);
       object[] GetAllInstances(Type type);
       T[] GetAllInstances<T>();
    }

    That's a lot for a concrete type to define, though, when half the methods will always just defer implementation to the other methods.  Better to use extension methods, here.
    public interface IServiceLocator
    {
       object GetInstance(Type type);
       object GetInstance(Type type, string name);
       object[] GetAllIntances(Type type);
    }
    
    
    public static class ServiceLocatorExtensions
    {
       public static T GetInstance<T>(this IServiceLocator self)
       {
          return (T)self.GetInstance(typeof(T));
       }
       public static T GetInstance<T>(this IServiceLocator self, string name)
       {
          return (T)self.GetInstance(typeof(T), name);
       }
       public static T[] GetAllInstances<T>()
       {
          return self.GetAllInstances(typeof(T)).Cast<T>().ToArray();
       }
    }

    Wow, Chrome doesn't post the code tags very well.
    Yea thats certainly what I use it for most, removing the weight off interfaces (arguably interfaces shouldn't enforce the overloads, but then you have to have the callers all handle overload handling theirself which is beating DRY with a stick and asking for bugs).. the best way then would be to use a utility class to make the call for you, but then that ends up looking weird.. along come extension methods and make it look natural.

  • cottsak

    this is totally what i hav been looking for. awesome!

  • evildictait​or

    Dr Herbie said:
    stevo_ said:
    *snip*
    Calling on null object ... hmm, I must add catches for that ...

    I'm using the extensions for flow syntax methods to match the property setters supported by the interface (because I want to support both), so the extension method ALWAYS calls the instance it's called on. I'm using it mainly for dependency injection:


    IMyInterface instance = new Implementation
    .WithPreProcessor(new ConcretePreprocessor())
    .WithPostProcessor(new ConcretePostProcessor());


    So what were you using them for, stevo_ ?

    Herbie
    Calling on null object ... hmm, I must add catches for that ...

    Why? Extention methods are intended to look like instance calls, which throw a NullReferenceException if the instance is null:

    object o = null;
    string f = o.ToString();   // throws a NullReferenceException at runtime, but not because Object::ToString() is checking.

    If you use extention methods, then by specifically coding for the null-case you're arguably breaking the semantics:


        IMyInterface item = null
        item.ExtensionMethod().SomeProperty = 5;

    is therefore an error at the call-site and not in the method. If you do code around it, be sure to throw a NullReferenceException, but if I were you I'd just leave it in a Debug.Assert() at the top of the method. In fact, if the method returned something at all, it would be breaking the semantics of object-calls.

  • stevo_

    Sounds right because these are instance calls usually, but in reality they are just static helper classes, what happens if a language calls into them directly which doesn't support extension methods? what if you just call into them directly anyway? I wonder if they wrote any official guidelines for this.

  • evildictait​or

    stevo_ said:
    Sounds right because these are instance calls usually, but in reality they are just static helper classes, what happens if a language calls into them directly which doesn't support extension methods? what if you just call into them directly anyway? I wonder if they wrote any official guidelines for this.
    instance methods are effectively static methods with the LHS passed as a parameter "this". The reason the NullReferenceException is thrown is because the runtime checks (for virtual methods the hardware does it, for non-virtual ones the runtime does it). It's odd that they chose not to keep this system for extention methods, but in either case you shouldn't be relying on "special-cases" where the LHS is null, because the programmer should guarrantee it at the call site (at worst, Debug.Assert(thisParameter != null) )

  • Sven Groot

    evildictaitor said:
    stevo_ said:
    *snip*
    instance methods are effectively static methods with the LHS passed as a parameter "this". The reason the NullReferenceException is thrown is because the runtime checks (for virtual methods the hardware does it, for non-virtual ones the runtime does it). It's odd that they chose not to keep this system for extention methods, but in either case you shouldn't be relying on "special-cases" where the LHS is null, because the programmer should guarrantee it at the call site (at worst, Debug.Assert(thisParameter != null) )
    I just do what I do with regular methods, throw an ArgumentNullException.

  • stevo_

    evildictaitor said:
    stevo_ said:
    *snip*
    instance methods are effectively static methods with the LHS passed as a parameter "this". The reason the NullReferenceException is thrown is because the runtime checks (for virtual methods the hardware does it, for non-virtual ones the runtime does it). It's odd that they chose not to keep this system for extention methods, but in either case you shouldn't be relying on "special-cases" where the LHS is null, because the programmer should guarrantee it at the call site (at worst, Debug.Assert(thisParameter != null) )
    Yes but this isn't a CLR feature, its just a feature that the interpreted languages are encouraged to use, vb and c# compiler probably only betray extension methods in CIL because they have attributes on them.. I always think of extension methods as being static helper classes when I'm calling them, despite the fact the compiler is making me think this isn't the case..

    Again I think this could go either way, I'd like to see some official advise, otherwise I'm going for the safe option of treating them as static helper methods.

  • joechung

    evildictaitor said:
    stevo_ said:
    *snip*
    instance methods are effectively static methods with the LHS passed as a parameter "this". The reason the NullReferenceException is thrown is because the runtime checks (for virtual methods the hardware does it, for non-virtual ones the runtime does it). It's odd that they chose not to keep this system for extention methods, but in either case you shouldn't be relying on "special-cases" where the LHS is null, because the programmer should guarrantee it at the call site (at worst, Debug.Assert(thisParameter != null) )

    A code example to illustrate in case anyone else following the thread is lost:

    var values = Enumerable.Range(1, 10);
    var reversed = values.Reverse(); // Normal use case
    var alsoReversed = Enumerable.Reverse(values); // Also possible
    var ouch = Enumerable.Reverse(null); // What should happen here?

    Maybe one day we'll get non-nullable reference types in C# and VB.NET and not have to worry about ArgumentNullExceptions and NullReferenceExceptions any more.

  • evildictait​or

    joechung said:
    evildictaitor said:
    *snip*

    A code example to illustrate in case anyone else following the thread is lost:

    var values = Enumerable.Range(1, 10);
    var reversed = values.Reverse(); // Normal use case
    var alsoReversed = Enumerable.Reverse(values); // Also possible
    var ouch = Enumerable.Reverse(null); // What should happen here?

    Maybe one day we'll get non-nullable reference types in C# and VB.NET and not have to worry about ArgumentNullExceptions and NullReferenceExceptions any more.

    var ouch = Enumerable.Reverse(null); // What should happen here?

    That's fine, it's more the case of

    ((IEnumerable<int>)null).Reverse() which is the problem. If Reverse is an instance method, it's immediately a NullReferenceException (since "." on anything that is null causes a NullReferenceException), but if it's a static type extention, it'll call "object Reverse(this IEnumerable<T> thisparam)) with thisparam = null, a breach of semantics.

    Frankly the compiler and runtime should collude in such a way that useing type-extentions forces an explicit null-check. If you're willing to call it in it's parameterised form Enumerable.Reverse(null) then it's just a null parameter, rather than a null "this" parameter.

    @stevo: you can think of all non-virtual and non-interfaced instance methods as static helper functions if you want (that's how they are implemented after all). The fact that "." causes a NullReferenceException is because the runtime provides an explicit check. For fields and virtual methods the check is performed by the hardware, but the principle is the same. Only static helper methods break the semantic of allowing null on the left-hand-side of a ".".

  • stevo_

    evildictaitor said:
    joechung said:
    *snip*

    var ouch = Enumerable.Reverse(null); // What should happen here?

    That's fine, it's more the case of

    ((IEnumerable<int>)null).Reverse() which is the problem. If Reverse is an instance method, it's immediately a NullReferenceException (since "." on anything that is null causes a NullReferenceException), but if it's a static type extention, it'll call "object Reverse(this IEnumerable<T> thisparam)) with thisparam = null, a breach of semantics.

    Frankly the compiler and runtime should collude in such a way that useing type-extentions forces an explicit null-check. If you're willing to call it in it's parameterised form Enumerable.Reverse(null) then it's just a null parameter, rather than a null "this" parameter.

    @stevo: you can think of all non-virtual and non-interfaced instance methods as static helper functions if you want (that's how they are implemented after all). The fact that "." causes a NullReferenceException is because the runtime provides an explicit check. For fields and virtual methods the check is performed by the hardware, but the principle is the same. Only static helper methods break the semantic of allowing null on the left-hand-side of a ".".

    Again my point is that you are talking about a language feature that makes up how the CLR works, extension methods are an abstraction in the written languages.. just happens that the main two languages agreed they both needed to support them.

Comments closed

Comments have been closed since this content was published more than 30 days ago, but if you'd like to continue the conversation, please create a new thread in our Forums, or Contact Us and let us know.