Частичный универсальный вывод типа, возможный в C#?

Я работаю над перезаписью моего быстрого интерфейса для моей библиотеки классов МОК, и когда я осуществил рефакторинг некоторый код для совместного использования некоторой общей функциональности через базовый класс, я натолкнулся на препятствие.

Примечание: Это - что-то, что я хочу сделать, не что-то, что я должен сделать. Если я должен суметь обойтись другим синтаксисом, я буду, но если у кого-либо есть идея о том, как заставить мой код скомпилировать способ, которым я хочу его, это приветствовалось бы.

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

Лучше с примером кода, чем вышеупомянутое описание, мне кажется.

Вот простой и полный пример того, что не работает:

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

Который так же плох.

Я могу легко реструктурировать классы или даже сделать методы non-extension-methods путем введения их в иерархию, но мой вопрос состоит в том, если я могу избежать необходимости копировать методы для этих двух потомков и в некотором роде объявить их только однажды для базового класса.

Позвольте мне перефразировать это. Существует ли способ изменить классы в первом примере кода выше, так, чтобы синтаксис в Основном методе мог быть сохранен, не копируя рассматриваемые методы?

Код должен будет быть совместим и с 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.

20
задан angry person 23 May 2010 в 22:30
поделиться

4 ответа

Если у вас есть только два конкретных типа регистрации (что, похоже, имеет место в вашем вопросе), вы можете просто реализовать два метода расширения:

public static DelegateRegistration Parameter<T>( 
   this DelegateRegistration p, string name, T value); 

public static ConcreteTypeRegistration Parameter<T>( 
   this ConcreteTypeRegistration p, string name, T value); 

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


В общем, C # не поддерживает ничего вроде o.Foo (..) для вывода только второго параметра типа (это было бы неплохо - в F # он есть, и он весьма полезно :-)). Вероятно, вы могли бы реализовать обходной путь, который позволил бы вам написать это (в основном, разделив вызов на два вызова методов, чтобы получить два места, где можно применить определение типа):

FooTrick<int>().Apply(); // where Apply is a generic method

Вот псевдокод для демонстрации структура:

// in the original object
FooImmediateWrapper<T> FooTrick<T>() { 
  return new FooImmediateWrapper<T> { InvokeOn = this; } 
}
// in the FooImmediateWrapper<T> class
(...) Apply<R>(arguments) { 
  this.InvokeOn.Foo<T, R>(arguments);
}
12
ответ дан 30 November 2019 в 00:39
поделиться

Как насчет следующего:

Используйте предоставленное вами определение: общедоступный статический параметр TReg ( это TReg p, имя строки, значение T) где TReg: ParameterizedRegistrationBase

Затем приведите параметр, чтобы машина вывода получила правильный тип:

ServiceContainerBuilder.Register<ISomeService>(r => r
.From(f => f.ConcreteType<FileService>(ct => ct
    .Parameter("source", (Stream)new FileStream(...)))));
1
ответ дан 30 November 2019 в 00:39
поделиться

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

2
ответ дан 30 November 2019 в 00:39
поделиться

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

Предположим, вы объявили класс-оболочку:

public class TypedValue<TValue>
{
    public TypedValue(TValue value)
    {
        Value = value;
    }

    public TValue Value { get; private set; }
}

Затем ваш метод расширения выглядит следующим образом:

public static class Extensions
{
    public static TReg Parameter<TValue, TReg>(
        this TReg p, string name, TypedValue<TValue> value) 
        where TReg : ParameterizedRegistrationBase
    {
        // can get at value.Value
        return p;
    }
}

Плюс более простая перегрузка (приведенная выше может на самом деле вызывать эту):

public static class Extensions
{
    public static TReg Parameter<TValue, TReg>(
        this TReg p, string name, TValue value) 
        where TReg : ParameterizedRegistrationBase
    {
        return p;
    }
}

Теперь в простом случае, когда вы счастливы вывести тип значения параметра:

ct.Parameter("name", "Lasse")

Но в случае, когда вам нужно явно указать тип, вы можете сделать так:

ct.Parameter("list", new TypedValue<IEnumerable<int>>(new List<int>()))

Выглядит некрасиво, но, надеюсь, реже, чем простой полностью выведенный тип.

Обратите внимание, что вы можете просто получить перегрузку без оболочки и написать:

ct.Parameter("list", (IEnumerable<int>)(new List<int>()))

Но у этого, конечно, есть недостаток - сбой во время выполнения, если вы ошиблись. К сожалению, сейчас мой компилятор C # отсутствует, так что извиняюсь, если это не так.

0
ответ дан 30 November 2019 в 00:39
поделиться
Другие вопросы по тегам:

Похожие вопросы: