问题
I have a wrapper generic class that intended to be used with a set of types. Those types are generated by a utility and are all derived from a base class ClientBase. While ClientBase has only a default constructor, all generated types have default constructor as well as a constructor takes a string as parameter. In the constructor of the wrapper class, I instantiate an instance of the type with the constructor that takes a string. Here is a sample code:
public class ClientBase
{ }
public class GenericProxy<T>
where T: ClientBase, new()
{
T _proxy;
public GenericProxy(string configName)
{
_proxy = new T(configName);
}
}
This code does not compile because type T is not guaranteed to have a constructor that takes a string. Is there a way to define a constrain on the generic class to enforce that the type T must have a constructor that take a string? If this is not possible, what are good alternatives to handle this kind of situation?
回答1:
It's not possible. I'd like to see "static interfaces" to handle this, but don't expect them any time soon...
Alternatives:
- Specify a delegate to act as a factory for T
- Specify another interface to act as a factory for T
- Specify an interface on T itself for initialization (and add a constraint so that
Timplements the interface)
The first two are really equivalent. Basically you'd change your proxy class to something like this:
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);
}
}
(I assume you're going to want to create more instances later - otherwise you might as well pass an instance of T into the constructor.)
回答2:
Unfortunately what you're trying to do isn't possible.
MSDN article on Type Constraints
回答3:
This does not answer your actual question, constraining a method, but for completeness here's how you can do what you're asking at run time, using reflection:
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 });
}
回答4:
As Jon notes, there is no inbuilt support for this - but as an aside you can create a typed delegate to the constructor (faster than reflection) using Expression. The code to do this can be found in MiscUtil (in MiscUtil.Linq.Extensions.TypeExt).
回答5:
Here is a full working example based on @JonSkeet answer:
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; }
}
}
Expect to see the following output: ice cream
来源:https://stackoverflow.com/questions/853703/how-to-constrain-generic-type-to-must-have-a-construtor-that-takes-certain-param