Подтвердить что ты не робот

Виртуальные методы расширения?

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

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

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

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

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

4b9b3361

Ответ 1

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

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

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

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

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

Ответ 2

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

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());
            }
        }
    }
}

Ответ 3

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

Ответ 4

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

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

Ответ 5

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

static IDictionary<Type, IService> serviceRegister;

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

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

Ответ 6

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

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

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