覆盖LINQ扩展方法

时间:2010-04-24 19:38:13

标签: c# .net linq mono extension-methods

有没有办法覆盖扩展方法(提供更好的实现),而不必明确地转换它们?我正在实现一种能够比默认扩展方法更有效地处理某些操作的数据类型,但我想保持IEnumerable的通用性。这样就可以传递任何IEnumerable,但是当我的类被传入时,它应该更有效。

作为玩具示例,请考虑以下事项:

// Compile: dmcs -out:test.exe test.cs

using System;

namespace Test {
    public interface IBoat {
        void Float ();
    }

    public class NiceBoat : IBoat {
        public void Float () {
            Console.WriteLine ("NiceBoat floating!");
        }
    }

    public class NicerBoat : IBoat {
        public void Float () {
            Console.WriteLine ("NicerBoat floating!");
        }

        public void BlowHorn () {
            Console.WriteLine ("NicerBoat: TOOOOOT!");
        }
    }

    public static class BoatExtensions {
        public static void BlowHorn (this IBoat boat) {
            Console.WriteLine ("Patched on horn for {0}: TWEET", boat.GetType().Name);
        }
    }

    public class TestApp {
        static void Main (string [] args) {
            IBoat niceboat = new NiceBoat ();
            IBoat nicerboat = new NicerBoat ();

            Console.WriteLine ("## Both should float:");
            niceboat.Float ();
            nicerboat.Float ();
            // Output:
            //      NiceBoat floating!
            //      NicerBoat floating!

            Console.WriteLine ();
            Console.WriteLine ("## One has an awesome horn:");
            niceboat.BlowHorn ();
            nicerboat.BlowHorn ();
            // Output:
            //      Patched on horn for NiceBoat: TWEET
            //      Patched on horn for NicerBoat: TWEET

            Console.WriteLine ();
            Console.WriteLine ("## That didn't work, but it does when we cast:");
            (niceboat as NiceBoat).BlowHorn ();
            (nicerboat as NicerBoat).BlowHorn ();
            // Output:
            //      Patched on horn for NiceBoat: TWEET
            //      NicerBoat: TOOOOOT!

            Console.WriteLine ();
            Console.WriteLine ("## Problem is: I don't always know the type of the objects.");
            Console.WriteLine ("## How can I make it use the class objects when the are");
            Console.WriteLine ("## implemented and extension methods when they are not,");
            Console.WriteLine ("## without having to explicitely cast?");
        }
    }
}

有没有办法从第二种情况获得行为,没有明确的演员?可以避免这个问题吗?

1 个答案:

答案 0 :(得分:15)

扩展方法是静态方法,您不能覆盖静态方法。也不能用静态/扩展方法“覆盖”实际的实例方法。

您必须明确使用优化的扩展程序。或者隐式地引用您自己的扩展名的命名空间而不是System.Linq

或者显式检查扩展中的类型,并根据运行时类型调用正确的类型。

这似乎是一个比扩展方法更适合继承的问题。如果您想要基于运行时类型的不同功能,那么将基本方法设为虚拟并在派生类中覆盖它。

我发现扩展方法的这方面存在很多困惑。你必须明白它们不是mixins,它们实际上并没有被注入到课堂中。它们只是编译器识别的语法糖,并且“允许”您执行它,就像它是常规实例方法一样。想象一下,它不是一个扩展方法,而只是一个静态方法:

public static void BlowHorn (IBoat boat) {
    Console.WriteLine ("Patched on horn for {0}: TWEET", boat.GetType().Name);
}

您如何从IBoat实施中“覆盖”此方法?你不能。您唯一能做的就是将类型检查放入此静态方法中,或者使用C#4中的dynamic块或早期版本中的Reflection来编写一些动态方法调用代码。

为了使这一点更加清晰,请查看Reflector中System.Linq.Enumerable类的代码:

public static TSource ElementAt<TSource>(this IEnumerable<TSource> source, 
    int index)
{
    TSource current;
    if (source == null)
    {
        throw Error.ArgumentNull("source");
    }
        IList<TSource> list = source as IList<TSource>;
    if (list != null)
    {
        return list[index];
    }
// ...
}

这是.NET Framework中的核心扩展方法之一。它允许通过显式检查参数是否实现IList<T>来进行优化。除此之外,它无法知道底层具体类型是否实际上支持索引访问。你必须以同样的方式做到这一点;创建另一个界面,例如IHorn或其他内容,在您的扩展程序中,检查IBoat是否也实现了IHorn,与此处的Enumerable类相同。

如果您不控制IBoat类或扩展方法的代码,那么您运气不好。如果你这样做,那么使用多接口继承,显式类型检查或动态代码,这些都是你的选择。