Переопределение методов расширения LINQ - PullRequest
5 голосов
/ 24 апреля 2010

Есть ли способ переопределить методы расширения (обеспечить лучшую реализацию) без явного приведения к ним? Я реализую тип данных, который способен обрабатывать определенные операции более эффективно, чем методы расширения по умолчанию, но я хотел бы сохранить общность 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 Ответ

15 голосов
/ 24 апреля 2010

Методы расширения являются статическими, и вы не можете переопределить статический метод. Вы также не можете "переопределить" фактический метод экземпляра статическим / расширением.

Вам нужно будет явно использовать оптимизированное расширение. Или неявно, ссылаясь на пространство имен вашего собственного расширения вместо System.Linq.

Или явно проверьте тип в вашем расширении и вызовите правильный тип в зависимости от типа среды выполнения.

Это похоже на проблему, лучше подходящую для наследования, чем методы расширения. Если вам нужны другие функции в зависимости от типа среды выполнения, сделайте базовый метод виртуальным и переопределите его в производных классах.

Я вижу много путаницы в этом аспекте методов расширения. Вы должны понимать, что они не миксины, они на самом деле не попадают в класс. Это просто синтаксический сахар, который компилятор распознает и «позволяет» выполнить его, как если бы это был обычный метод экземпляра. Представьте, что это был не метод расширения, а просто статический метод:

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

Как бы вы "переопределили" этот метод из реализации IBoat? Ты не можешь Единственное, что вы можете сделать, это включить проверку типов в этот статический метод или написать некоторый код вызова динамического метода, используя блок dynamic в C # 4 или Reflection в более ранних версиях.

Чтобы сделать это еще более понятным, взгляните на этот код класса System.Linq.Enumerable из Reflector:

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 классов или методов расширения, то вам не повезло. Если вы это сделаете, то используйте многоинтерфейсное наследование, явную проверку типов или динамический код - это ваши варианты.

...