Как ограничить общий тип, должен иметь конструктор, который принимает определенные параметры?

У меня есть общий класс обертки, который предназначен для использования с набором типов. Эти типы генерируются утилитой и все они получены из базового класса ClientBase. Хотя ClientBase имеет только конструктор по умолчанию, все сгенерированные типы имеют конструктор по умолчанию, а конструктор берет строку как параметр. В конструкторе класса-оболочки я создаю экземпляр типа с конструктором, который берет строку. Вот пример кода:

public class ClientBase
{ }

public class GenericProxy<T>
    where T: ClientBase, new()
{
    T _proxy;

    public GenericProxy(string configName)
    {
        _proxy = new T(configName);    
    }
}

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

Ответ 1

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

Альтернатива:

  • Задайте делегату действие factory для T
  • Укажите другой интерфейс, чтобы действовать как factory для T
  • Определите интерфейс для самого T для инициализации (и добавьте ограничение, чтобы T реализовал интерфейс)

Первые два действительно эквивалентны. В основном вы измените свой прокси-класс на что-то вроде этого:

public class GenericProxy<T>
    where T: ClientBase, new()
{
    string _configName;
    T _proxy;
    Func<string, T> _factory;

    public GenericProxy(Func<string, T> factory, string configName)
    {
        _configName = configName;
        _factory = factory;
        RefreshProxy();
    }

    void RefreshProxy() // As an example; suppose we need to do this later too
    {
        _proxy = _factory(_configName);
    }
}

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

Ответ 3

Как отмечает Джон, для этого нет встроенной поддержки, но в качестве альтернативы вы можете создать типизированный делегат для конструктора (быстрее, чем отражение) с помощью Expression. Код для этого можно найти в MiscUtilMiscUtil.Linq.Extensions.TypeExt).

Ответ 4

Это не отвечает на ваш реальный вопрос, сдерживая метод, но для полноты здесь вы можете делать то, что вы просите во время выполнения, используя отражение:

private T  Get<T>(string id)
    {
    var  constructor = typeof(T).GetConstructor(new Type[] { typeof(X), typeof(Y) });
    if (constructor == null)  throw new InvalidOperationException("The type submitted, " + typeof(T).Name + ", does not support the expected constructor (X, Y).");

    var  data = GetData(id);
    return (T)constructor.Invoke(new object[] { data.x, data.y });
    }

Ответ 5

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

using System;
using System.Collections.Generic;

namespace GenericProxy
{
    class Program
    {
        static void Main()
        {
            GenericProxy<ClientBase> proxy = new GenericProxy<ClientBase>(ClientBase.Factory, "cream");

            Console.WriteLine(proxy.Proxy.ConfigName); // test to see it working
        }
    }

    public class ClientBase
    {
        static public ClientBase Factory(string configName)
        {
            return new ClientBase(configName);
        }

        // default constructor as required by new() constraint
        public ClientBase() { }

        // constructor that takes arguments
        public ClientBase(string configName) { _configName = configName; }

        // simple method to demonstrate working example
        public string ConfigName
        {
            get { return "ice " + _configName; }
        }

        private string _configName;
    }

    public class GenericProxy<T>
        where T : ClientBase, new()
    {
        public GenericProxy(Func<string, T> factory, string configName)
        {
            Proxy = factory(configName);
        }

        public T Proxy { get; private set; }
    }
}

Ожидайте увидеть следующий вывод: ice cream