可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I\'m trying to create a new object of type T via its constructor when adding to the list.
I\'m getting a compile error: The error message is:
\'T\': cannot provide arguments when creating an instance of a variable
But my classes do have a constructor argument! How can I make this work?
public static string GetAllItems<T>(...) where T : new()
{
...
List<T> tabListItems = new List<T>();
foreach (ListItem listItem in listCollection)
{
tabListItems.Add(new T(listItem)); // error here.
}
...
}
回答1:
In order to create an instance of a generic type in a function you must constrain it with the \"new\" flag.
public static string GetAllItems<T>(...) where T : new()
However that will only work when you want to call the constructor which has no parameters. Not the case here. Instead you\'ll have to provide another parameter which allows for the creation of object based on parameters. The easiest is a function.
public static string GetAllItems<T>(..., Func<ListItem,T> del) {
...
List<T> tabListItems = new List<T>();
foreach (ListItem listItem in listCollection)
{
tabListItems.Add(del(listItem));
}
...
}
You can then call it like so
GetAllItems<Foo>(..., l => new Foo(l));
回答2:
in .Net 3.5 and after you could use the activator class:
(T)Activator.CreateInstance(typeof(T), args)
回答3:
Since nobody bothered to post the \'Reflection\' answer (which I personally think is the best answer), here goes:
public static string GetAllItems<T>(...) where T : new()
{
...
List<T> tabListItems = new List<T>();
foreach (ListItem listItem in listCollection)
{
Type classType = typeof(T);
ConstructorInfo classConstructor = classType.GetConstructor(new Type[] { listItem.GetType() });
T classInstance = (T)classConstructor.Invoke(new object[] { listItem });
tabListItems.Add(classInstance);
}
...
}
Edit: This answer is deprecated due to .NET 3.5\'s Activator.CreateInstance, however it is still useful in older .NET versions.
回答4:
Object initializer
If your constructor with the parameter isn\'t doing anything besides setting a property, you can do this in C# 3 or better using an object initializer rather than calling a constructor (which is impossible, as has been mentioned):
public static string GetAllItems<T>(...) where T : new()
{
...
List<T> tabListItems = new List<T>();
foreach (ListItem listItem in listCollection)
{
tabListItems.Add(new T() { YourPropertyName = listItem } ); // Now using object initializer
}
...
}
Using this, you can always put any constructor logic in the default (empty) constructor, too.
Activator.CreateInstance()
Alternatively, you could call Activator.CreateInstance() like so:
public static string GetAllItems<T>(...) where T : new()
{
...
List<T> tabListItems = new List<T>();
foreach (ListItem listItem in listCollection)
{
object[] args = new object[] { listItem };
tabListItems.Add((T)Activator.CreateInstance(typeof(T), args)); // Now using Activator.CreateInstance
}
...
}
Note that Activator.CreateInstance can have some performance overhead that you may want to avoid if execution speed is a top priority and another option is maintainable to you.
回答5:
This will not work in your situation. You can only specify the constraint that it has an empty constructor:
public static string GetAllItems<T>(...) where T: new()
What you could do is use property injection by defining this interface:
public interface ITakesAListItem
{
ListItem Item { set; }
}
Then you could alter your method to be this:
public static string GetAllItems<T>(...) where T : ITakesAListItem, new()
{
...
List<T> tabListItems = new List<T>();
foreach (ListItem listItem in listCollection)
{
tabListItems.Add(new T() { Item = listItem });
}
...
}
The other alternative is the Func
method described by JaredPar.
回答6:
Very old question, but new answer ;-)
The ExpressionTree version: (I think the fastests and cleanest solution)
Like Welly Tambunan said, \"we could also use expression tree to build the object\"
This will generate a \'constructor\' (function) for the type/parameters given. It returns a delegate and accept the parameter types as an array of objects.
Here it is:
// this delegate is just, so you don\'t have to pass an object array. _(params)_
public delegate object ConstructorDelegate(params object[] args);
public static ConstructorDelegate CreateConstructor(Type type, params Type[] parameters)
{
// Get the constructor info for these parameters
var constructorInfo = type.GetConstructor(parameters);
// define a object[] parameter
var paramExpr = Expression.Parameter(typeof(Object[]));
// To feed the constructor with the right parameters, we need to generate an array
// of parameters that will be read from the initialize object array argument.
var constructorParameters = parameters.Select((paramType, index) =>
// convert the object[index] to the right constructor parameter type.
Expression.Convert(
// read a value from the object[index]
Expression.ArrayAccess(
paramExpr,
Expression.Constant(index)),
paramType)).ToArray();
// just call the constructor.
var body = Expression.New(constructorInfo, constructorParameters);
var constructor = Expression.Lambda<ConstructorDelegate>(body, paramExpr);
return constructor.Compile();
}
Example MyClass:
public class MyClass
{
public int TestInt { get; private set; }
public string TestString { get; private set; }
public MyClass(int testInt, string testString)
{
TestInt = testInt;
TestString = testString;
}
}
Usage:
// you should cache this \'constructor\'
var myConstructor = CreateConstructor(typeof(MyClass), typeof(int), typeof(string));
// Call the `myConstructor` fucntion to create a new instance.
var myObject = myConstructor(10, \"test message\");
Another example: passing the types as an array
var type = typeof(MyClass);
var args = new Type[] { typeof(int), typeof(string) };
// you should cache this \'constructor\'
var myConstructor = CreateConstructor(type, args);
// Call the `myConstructor` fucntion to create a new instance.
var myObject = myConstructor(10, \"test message\");
DebugView of Expression
.Lambda #Lambda1<TestExpressionConstructor.MainWindow+ConstructorDelegate>(System.Object[] $var1) {
.New TestExpressionConstructor.MainWindow+MyClass(
(System.Int32)$var1[0],
(System.String)$var1[1])
}
This is equivalent to the code that is generated:
public object myConstructor(object[] var1)
{
return new MyClass(
(System.Int32)var1[0],
(System.String)var1[1]);
}
Small downside
All valuetypes parameters are boxed when they are passed like an object array.
Simple performance test:
private void TestActivator()
{
Stopwatch sw = Stopwatch.StartNew();
for (int i = 0; i < 1024 * 1024 * 10; i++)
{
var myObject = Activator.CreateInstance(typeof(MyClass), 10, \"test message\");
}
sw.Stop();
Trace.WriteLine(\"Activator: \" + sw.Elapsed);
}
private void TestReflection()
{
var constructorInfo = typeof(MyClass).GetConstructor(new[] { typeof(int), typeof(string) });
Stopwatch sw = Stopwatch.StartNew();
for (int i = 0; i < 1024 * 1024 * 10; i++)
{
var myObject = constructorInfo.Invoke(new object[] { 10, \"test message\" });
}
sw.Stop();
Trace.WriteLine(\"Reflection: \" + sw.Elapsed);
}
private void TestExpression()
{
var myConstructor = CreateConstructor(typeof(MyClass), typeof(int), typeof(string));
Stopwatch sw = Stopwatch.StartNew();
for (int i = 0; i < 1024 * 1024 * 10; i++)
{
var myObject = myConstructor(10, \"test message\");
}
sw.Stop();
Trace.WriteLine(\"Expression: \" + sw.Elapsed);
}
TestActivator();
TestReflection();
TestExpression();
Results:
Activator: 00:00:13.8210732
Reflection: 00:00:05.2986945
Expression: 00:00:00.6681696
Using Expressions
is +/- 8 times faster than Invoking the ConstructorInfo
and +/- 20 times faster than using the Activator
回答7:
You need to add where T: new() to let the compiler know that T is guaranteed to provide a default constructor.
public static string GetAllItems<T>(...) where T: new()
回答8:
If you simply want to initialise a member field or property with the constructor parameter, in C# >= 3 you can do it very easier:
public static string GetAllItems<T>(...) where T : InterfaceOrBaseClass, new()
{
...
List<T> tabListItems = new List<T>();
foreach (ListItem listItem in listCollection)
{
tabListItems.Add(new T{ BaseMemberItem = listItem }); // No error, BaseMemberItem owns to InterfaceOrBaseClass.
}
...
}
This is the same thing Garry Shutler said, but I\'d like to put an aditional note.
Of course you can use a property trick to do more stuff than just setting a field value.
A property \"set()\" can trigger any processing needed to setup its related fields and any other need for the object itself, including a check to see if a full initialization is to take place before the object is used, simulating a full contruction (yes, it is an ugly workaround, but it overcomes M$\'s new() limitation).
I can\'t be assure if it is a planned hole or an accidental side effect, but it works.
It is very funny how M$ people adds new features to the language and seems to not do a full side effects analysis.
The entire generic thing is a good evidence of this...
回答9:
I found that I was getting an error \"cannot provide arguments when creating an instance of type parameter T\" so I needed to do this:
var x = Activator.CreateInstance(typeof(T), args) as T;
回答10:
If you have access to the class you\'re going to use, you can use this approach which I used.
Create an interface that has an alternative creator:
public interface ICreatable1Param
{
void PopulateInstance(object Param);
}
Make your classes with an empty creator and implement this method:
public class MyClass : ICreatable1Param
{
public MyClass() { //do something or nothing }
public void PopulateInstance (object Param)
{
//populate the class here
}
}
Now use your generic methods:
public void MyMethod<T>(...) where T : ICreatable1Param, new()
{
//do stuff
T newT = new T();
T.PopulateInstance(Param);
}
If you don\'t have access, wrap the target class:
public class MyClass : ICreatable1Param
{
public WrappedClass WrappedInstance {get; private set; }
public MyClass() { //do something or nothing }
public void PopulateInstance (object Param)
{
WrappedInstance = new WrappedClass(Param);
}
}
回答11:
I sometimes use an approach that resembles to the answers using property injection, but keeps the code cleaner.
Instead of having a base class/interface with a set of properties, it only contains a (virtual) Initialize()-method that acts as a \"poor man\'s constructor\".
Then you can let each class handle it\'s own initialization just as a constructor would, which also adds a convinient way of handling inheritance chains.
If often find myself in situations where I want each class in the chain to initialize its unique properties, and then call its parent\'s Initialize()-method which in turn initializes the parent\'s unique properties and so forth. This is especially useful when having different classes, but with a similar hierarchy, for example business objects that are mapped to/from DTO:s.
Example that uses a common Dictionary for initialization:
void Main()
{
var values = new Dictionary<string, int> { { \"BaseValue\", 1 }, { \"DerivedValue\", 2 } };
Console.WriteLine(CreateObject<Base>(values).ToString());
Console.WriteLine(CreateObject<Derived>(values).ToString());
}
public T CreateObject<T>(IDictionary<string, int> values)
where T : Base, new()
{
var obj = new T();
obj.Initialize(values);
return obj;
}
public class Base
{
public int BaseValue { get; set; }
public virtual void Initialize(IDictionary<string, int> values)
{
BaseValue = values[\"BaseValue\"];
}
public override string ToString()
{
return \"BaseValue = \" + BaseValue;
}
}
public class Derived : Base
{
public int DerivedValue { get; set; }
public override void Initialize(IDictionary<string, int> values)
{
base.Initialize(values);
DerivedValue = values[\"DerivedValue\"];
}
public override string ToString()
{
return base.ToString() + \", DerivedValue = \" + DerivedValue;
}
}
回答12:
This is kind of mucky, and when I say kind of mucky I may mean revolting, but supposing you can furnish your parameterised type with an empty constructor, then:
public static T GetTInstance<T>() where T: new()
{
var constructorTypeSignature = new Type[] {typeof (object)};
var constructorParameters = new object[] {\"Create a T\"};
return (T) new T().GetType().GetConstructor(constructorTypeSignature).Invoke(constructorParameters);
}
Will effectively allow you to construct an object from a parameterised type with an argument. In this case I am assuming the constructor I want has a single argument of type object
. We create a dummy instance of T using the constraint permitted empty constructor and then use reflection to get one of its other constructors.
回答13:
I believe you have to constraint T with a where statement to only allow objects with a new constructor.
RIght now it accepts anything including objects without it.