Я работаю над переписыванием моего свободного интерфейса для своей библиотеки классов IoC, и когда я реорганизовал некоторый код, чтобы разделить некоторые общие функции через базовый класс, я наткнулся на загадку.
Примечание : Это то, что я хочу сделать, а не то, что я должен сделать . Если мне придется обойтись с другим синтаксисом, я это сделаю, но если у кого-то есть идея, как заставить мой код компилироваться так, как я хочу, это было бы очень кстати.
Я хочу, чтобы некоторые методы расширения были доступны для определенного базового класса, и эти методы должны быть универсальными, с одним универсальным типом, связанным с аргументом метода, но методы также должны возвращать определенный тип, связанный с конкретный потомок, на которого они призваны.
Лучше с примером кода, чем приведенное выше описание.
Вот простой и полный пример того, что не работает:
using System;
namespace ConsoleApplication16
{
public class ParameterizedRegistrationBase { }
public class ConcreteTypeRegistration : ParameterizedRegistrationBase
{
public void SomethingConcrete() { }
}
public class DelegateRegistration : ParameterizedRegistrationBase
{
public void SomethingDelegated() { }
}
public static class Extensions
{
public static ParameterizedRegistrationBase Parameter<T>(
this ParameterizedRegistrationBase p, string name, T value)
{
return p;
}
}
class Program
{
static void Main(string[] args)
{
ConcreteTypeRegistration ct = new ConcreteTypeRegistration();
ct
.Parameter<int>("age", 20)
.SomethingConcrete(); // <-- this is not available
DelegateRegistration del = new DelegateRegistration();
del
.Parameter<int>("age", 20)
.SomethingDelegated(); // <-- neither is this
}
}
}
Если вы скомпилируете это, вы получите:
'ConsoleApplication16.ParameterizedRegistrationBase' does not contain a definition for 'SomethingConcrete' and no extension method 'SomethingConcrete'...
'ConsoleApplication16.ParameterizedRegistrationBase' does not contain a definition for 'SomethingDelegated' and no extension method 'SomethingDelegated'...
Я хочу, чтобы метод расширения (Parameter<T>
) можно было вызывать как для ConcreteTypeRegistration
, так и DelegateRegistration
, и в обоих случаях возвращаемый тип должен соответствовать типу, для которого было вызвано расширение.
Проблема заключается в следующем:
Я хотел бы написать:
ct.Parameter<string>("name", "Lasse")
^------^
notice only one generic argument
но также, что Parameter<T>
возвращает объект того же типа, к которому он был вызван, что означает:
ct.Parameter<string>("name", "Lasse").SomethingConcrete();
^ ^-------+-------^
| |
+---------------------------------------------+
.SomethingConcrete comes from the object in "ct"
which in this case is of type ConcreteTypeRegistration
Можно ли каким-то образом обмануть компилятор, чтобы он совершил для меня этот скачок?
Если я добавлю два аргумента универсального типа в метод Parameter
, вывод типа заставит меня либо предоставить оба, либо ни одного, что означает следующее:
public static TReg Parameter<TReg, T>(
this TReg p, string name, T value)
where TReg : ParameterizedRegistrationBase
дает мне это:
Using the generic method 'ConsoleApplication16.Extensions.Parameter<TReg,T>(TReg, string, T)' requires 2 type arguments
Using the generic method 'ConsoleApplication16.Extensions.Parameter<TReg,T>(TReg, string, T)' requires 2 type arguments
Что так же плохо.
Я могу легко реструктурировать классы или даже сделать методы нерасширенными методами, вводя их в иерархию, но мой вопрос заключается в том, можно ли избежать дублирования методов для двух потомков и каким-то образом объявить их только один раз, для базового класса.
Позвольте мне перефразировать это. Есть ли способ изменить классы в первом примере кода выше, чтобы можно было сохранить синтаксис в методе Main без дублирования рассматриваемых методов?
Код должен быть совместим с C # 3.0 и 4.0.
Редактировать : Причина, по которой я не хотел бы оставлять оба аргумента универсального типа для вывода, состоит в том, что для некоторых служб я хочу указать значение параметра для параметра конструктора, который имеет один тип, но передает в значении, которое является потомком. На данный момент сопоставление указанных значений аргумента и правильного конструктора для вызова выполняется с использованием как имени, так и типа аргумента.
Позвольте мне привести пример:
ServiceContainerBuilder.Register<ISomeService>(r => r
.From(f => f.ConcreteType<FileService>(ct => ct
.Parameter<Stream>("source", new FileStream(...)))));
^--+---^ ^---+----^
| |
| +- has to be a descendant of Stream
|
+- has to match constructor of FileService
Если я оставлю оба параметра для вывода типа, тип параметра будет FileStream
, а не Stream
.