Виртуальные методы расширения? - PullRequest
12 голосов
/ 17 апреля 2009

У меня есть класс, который используется в клиентском приложении и серверном приложении. В серверном приложении я добавляю некоторые функциональные возможности в методы расширения класса. Работает отлично. Теперь я хочу немного больше:

Мой класс (B) наследуется от другого класса (A).

Я бы хотел присоединить виртуальную функцию к A (скажем, Execute ()), а затем реализовать эту функцию в B. Но только на сервере. Метод Execute () должен выполнять действия, которые можно выполнять только на сервере, используя типы, о которых знает только сервер.

Есть много типов, которые наследуются от A так же, как B, и я хотел бы реализовать Execute () для каждого из них.

Я надеялся, что смогу добавить метод виртуального расширения в A, но эта идея, похоже, не сработает. Я ищу самый элегантный способ решения этой проблемы, с или без методов расширения.

Ответы [ 6 ]

4 голосов
/ 17 апреля 2009

Нет, нет таких вещей, как виртуальные методы расширения. Вы можете использовать перегрузку, но это не поддерживает полиморфизм. Звучит так, как будто вы захотите взглянуть на что-то вроде внедрения зависимостей (и т. Д.), Чтобы в разных средах был добавлен другой код (зависимости), и использовать его в обычных виртуальных методах:

class B {
     public B(ISomeUtility util) {
         // store util
     }
     public override void Execute() {
         if(util != null) util.Foo();
     }
}

Затем используйте инфраструктуру DI для предоставления серверной реализации ISomeUtility для B во время выполнения. Вы можете сделать то же самое с центральным реестром static (IOC, но без DI):

    override void Execute() {
        ISomeUtility util = Registry.Get<ISomeUtility>();
        if(util != null) util.Foo();
    }

(там, где вам нужно написать Registry и т. Д .; плюс на сервере, зарегистрировать реализацию ISomeUtility)

3 голосов
/ 11 февраля 2011

Вы можете использовать новую функциональность динамического типа, чтобы избежать необходимости создавать реестр типов для методов:

using System;
using System.Collections.Generic;
using System.Linq;
using visitor.Extension;

namespace visitor
{
    namespace Extension
    {
        static class Extension
        {
            public static void RunVisitor(this IThing thing, IThingOperation thingOperation)
            {
                thingOperation.Visit((dynamic)thing);
            }

            public static ITransformedThing GetTransformedThing(this IThing thing, int arg)
            {
                var x = new GetTransformedThing {Arg = arg};
                thing.RunVisitor(x);
                return x.Result;
            }
        }
    }

    interface IThingOperation
    {
        void Visit(IThing iThing);
        void Visit(AThing aThing);
        void Visit(BThing bThing);
        void Visit(CThing cThing);
        void Visit(DThing dThing);
    }

    interface ITransformedThing { }

    class ATransformedThing : ITransformedThing { public ATransformedThing(AThing aThing, int arg) { } }
    class BTransformedThing : ITransformedThing { public BTransformedThing(BThing bThing, int arg) { } }
    class CTransformedThing : ITransformedThing { public CTransformedThing(CThing cThing, int arg) { } }
    class DTransformedThing : ITransformedThing { public DTransformedThing(DThing dThing, int arg) { } }

    class GetTransformedThing : IThingOperation
    {
        public int Arg { get; set; }

        public ITransformedThing Result { get; private set; }

        public void Visit(IThing iThing) { Result = null; }
        public void Visit(AThing aThing) { Result = new ATransformedThing(aThing, Arg); }
        public void Visit(BThing bThing) { Result = new BTransformedThing(bThing, Arg); }
        public void Visit(CThing cThing) { Result = new CTransformedThing(cThing, Arg); }
        public void Visit(DThing dThing) { Result = new DTransformedThing(dThing, Arg); }
    }

    interface IThing {}
    class Thing : IThing {}
    class AThing : Thing {}
    class BThing : Thing {}
    class CThing : Thing {}
    class DThing : Thing {}
    class EThing : Thing { }

    class Program
    {
        static void Main(string[] args)
        {
            var things = new List<IThing> { new AThing(), new BThing(), new CThing(), new DThing(), new EThing() };
            var transformedThings = things.Select(thing => thing.GetTransformedThing(4)).Where(transformedThing => transformedThing != null).ToList();
            foreach (var transformedThing in transformedThings)
            {
                Console.WriteLine(transformedThing.GetType().ToString());
            }
        }
    }
}
2 голосов
/ 17 апреля 2009

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

using System;
using System.Collections.Generic;

namespace LanguageTests2
{
    public class A { }

    public class B : A {}

    public class C : B {}

    public static class VirtualExtensionMethods
    {
        private static readonly IDictionary<Type,Action<A>> _dispatchMap 
            = new Dictionary<Type, Action<A>>();

        static VirtualExtensionMethods()
        {
            _dispatchMap[typeof(A)] = x => ExecuteInternal( (A)x );
            _dispatchMap[typeof(B)] = x => ExecuteInternal( (B)x );
            _dispatchMap[typeof(C)] = x => ExecuteInternal( (C)x );
        }

        public static void Execute( this A instance )
        {
            _dispatchMap[instance.GetType()]( instance );
        }

        private static void ExecuteInternal( A instance )
        {
            Console.WriteLine("\nCalled ToString() on: " + instance);
        }

        private static void ExecuteInternal(B instance)
        {
            Console.WriteLine( "\nCalled ToString() on: " + instance );
        }

        private static void ExecuteInternal(C instance)
        {
            Console.WriteLine("\nCalled ToString() on: " + instance);
        }
    }

    public class VirtualExtensionsTest
    {
        public static void Main()
        {
            var instanceA = new A();
            var instanceB = new B();
            var instanceC = new C();

            instanceA.Execute();
            instanceB.Execute();
            instanceC.Execute();
        }
    }
}
0 голосов
/ 17 апреля 2009

Позвольте мне проверить: у вас есть иерархия классов, унаследованная от A, предположительно структурированная в соответствии с вашей бизнес-областью. Затем вы хотите добавить поведение в зависимости от того, где выполняются классы. До сих пор вы использовали методы расширения, но теперь вы обнаружите, что не можете заставить их варьироваться в зависимости от вашей иерархии классов. Какие виды поведения вы применяете на сервере?

Если это такие вещи, как управление транзакциями и безопасность, политики, реализованные посредством внедрения зависимостей, как предложено Марком, должны работать хорошо. Вы также можете рассмотреть возможность реализации шаблона стратегии с помощью делегатов и лямбд для более ограниченной версии DI. Однако неясно, как клиентский код в настоящее время использует ваши классы и методы их расширения на сервере. Насколько другие классы зависят от того, как вы добавляете функциональность на стороне сервера? Являются ли они серверными классами, которые в настоящее время ожидают найти методы расширения?

В любом случае, похоже, что вам потребуется тщательная разработка и тестирование стратегии тестирования, так как вы вводите вариации в двух одновременных измерениях (иерархия наследования, среда выполнения). Вы пользуетесь юнит-тестированием? Убедитесь, что любое решение, которое вы выберете (например, DI через конфигурацию), хорошо взаимодействует с тестированием и макетом.

0 голосов
/ 17 апреля 2009

Вы можете реализовать сервисный реестр. Пример (на стороне сервера):

static IDictionary<Type, IService> serviceRegister;

public void ServerMethod(IBusinessType object)
{
  serviceRegister[obect.GetType()].Execute(object);
}

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

0 голосов
/ 17 апреля 2009

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

Проверьте ответ Марка Гравелла для возможного решения вашей проблемы.

...