Как ограничить общий тип, должен иметь конструктор, который принимает определенные параметры?
У меня есть общий класс обертки, который предназначен для использования с набором типов. Эти типы генерируются утилитой и все они получены из базового класса 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 в конструктор.)
Ответ 2
К сожалению, то, что вы пытаетесь сделать, невозможно.
Статья MSDN о ограничениях типов
Ответ 3
Как отмечает Джон, для этого нет встроенной поддержки, но в качестве альтернативы вы можете создать типизированный делегат для конструктора (быстрее, чем отражение) с помощью Expression
. Код для этого можно найти в MiscUtil (в MiscUtil.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