[聚合问答] Passing arguments to C# generic new() of templated type

c#,.net,generics,new-operator 2017-11-30 9 阅读

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.
   } 
   ...
}

10个回答

351

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));

2017-11-30
279

in .Net 3.5 and after you could use the activator class:

(T)Activator.CreateInstance(typeof(T), args)

2017-11-30
49

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.

2017-11-30
25

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.

2017-11-30
16

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.

2017-11-30
9

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");

enter image description here


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

2017-11-30
8

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()

2017-11-30
7

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...

2017-11-30
5

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;

2017-11-30
1

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;
    }
}

2017-11-30

注:本文内容来自互联网,旨在为开发者提供分享、交流的平台。如有涉及文章版本等事宜,请你联系站长进行处理。