Create instance of generic type whose constructor requires a parameter?
Asked Answered
S

12

328

If BaseFruit has a constructor that accepts an int weight, can I instantiate a piece of fruit in a generic method like this?

public void AddFruit<T>()where T: BaseFruit{
    BaseFruit fruit = new T(weight); /*new Apple(150);*/
    fruit.Enlist(fruitManager);
}

An example is added behind comments. It seems I can only do this if I give BaseFruit a parameterless constructor and then fill in everything through member variables. In my real code (not about fruit) this is rather impractical.

-Update-
So it seems it can't be solved by constraints in any way then. From the answers there are three candidate solutions:

  • Factory Pattern
  • Reflection
  • Activator

I tend to think reflection is the least clean one, but I can't decide between the other two.

Slaughterhouse answered 8/4, 2009 at 19:26 Comment(2)
BTW: today I would probably solve this with the IoC library of choice.Slaughterhouse
Reflection and Activator are actually closely related.Smutty
B
482

Additionally a simpler example:

return (T)Activator.CreateInstance(typeof(T), new object[] { weight });

Note that using the new() constraint on T is only to make the compiler check for a public parameterless constructor at compile time, the actual code used to create the type is the Activator class.

You will need to ensure yourself regarding the specific constructor existing, and this kind of requirement may be a code smell (or rather something you should just try to avoid in the current version on c#).

Bosporus answered 8/4, 2009 at 20:16 Comment(7)
Since this constructor is on the baseclass (BaseFruit) I know it will have a constructor. But indeed, if one day I decide basefruit needs more parameters, I could be screwed. Will look into the ACtivator class though. Didn't hear of it before.Slaughterhouse
This one worked out fine. There's also a CreateInstance<T>() procedure, but that doesn't have an overload for parameters for some rason..Slaughterhouse
There is no need to use new object[] { weight }. CreateInstance is declared with params, public static object CreateInstance(Type type, params object[] args), so you can just do return (T) Activator.CreateInstance(typeof(T), weight);. If there are multiple parameters, pass them in as separate arguments. Only if you already have a constructed enumerable of parameters should you bother to convert it to object[] and pass that to CreateInstance.Clarenceclarenceux
This will have performance issues I've read. Use a compiled lambda instead. vagifabilov.wordpress.com/2010/04/02/…Commentate
David, the article is unclear for me. Can you give a specific example of using compiled lambda instead?Smutty
@RobVermeulen - I think something like a static property on each Fruit class, that contains a Func which creates the new instance. Suppose Apple constructor usage is new Apple(wgt). Then add to Apple class this definition: static Func<float, Fruit> CreateOne { get; } = (wgt) => new Apple(wgt); In Factory define public static Fruit CreateFruitGiven(float weight, Func<float, Fruit> createOne) { return createOne(weight); } Usage: Factory.CreateFruit(57.3f, Apple.CreateOne); -- that creates and returns an Apple, with weight=57.3f.Alcaic
Of course in my simple example, one could instead simply say Apple.CreateOne(57.3f);. Or more directly new Apple(57.3f);. But the point here is that Apple.CreateOne is a compiled lambda (of type Func<float, Fruit>, which could be stored somewhere (e.g. in a dictionary in the Factory), or in data elsewhere, to be passed later to a Factory method, allowing "indirect" specification of the desired fruit.Alcaic
B
127

You can't use any parameterised constructor. You can use a parameterless constructor if you have a "where T : new()" constraint.

It's a pain, but such is life :(

This is one of the things I'd like to address with "static interfaces". You'd then be able to constrain T to include static methods, operators and constructors, and then call them.

Blastopore answered 8/4, 2009 at 19:28 Comment(7)
At least you CAN do such constraints - Java always disappoints me.Ventre
@JonSkeet: If I exposed the API with .NET generic to be called in VB6.0..Does it still workable?Rabat
@Roylee: I've no idea, but I suspect not.Blastopore
I would think static interfaces could be added by a language compiler without changes to the runtime, though it would be good to have language teams coordinate on the particulars. Specify that every class claiming to implement a static interface must contain a nested class with a particular interface-related name, which defines a static singleton instance of its own type. Associated with the interface would be a static generic type with an instance field which would need to be loaded with the singleton once via Reflection, but could be used directly after that.Acentric
A parameterized constructor constraint could be handled much the same way (using a factory method, and a generic parameter for its return type); in neither case would anything prevent code written in a language which didn't support such a feature from claiming to implement the interface without defining the proper static type, so code written using such languages could fail at runtime, but Reflection could be avoided in user code.Acentric
Static interfaces: want!Clarenceclarenceux
This is the better answerTehuantepec
N
85

Yes; change your where to be:

where T:BaseFruit, new()

However, this only works with parameterless constructors. You'll have to have some other means of setting your property (setting the property itself or something similar).

Naquin answered 8/4, 2009 at 19:28 Comment(2)
If the constructor doesn't have parameters this seems safe to me.Rett
You've saved my life. I couldn't manage to restrict T to class and new() keyword.Chirr
R
50

Most simple solution Activator.CreateInstance<T>()

Rybinsk answered 14/3, 2013 at 12:0 Comment(1)
Thanks for the suggestion, it got me where I needed to be. Although this does not allow you to use a parameterized constructor. But you could use the non-generic variant: Activator.CreateInstance(typeof(T), new object[] {...}) where the object array contains the arguments for the constructor.Smutty
Y
22

As Jon pointed out this is life for constraining a non-parameterless constructor. However a different solution is to use a factory pattern. This is easily constrainable

interface IFruitFactory<T> where T : BaseFruit {
  T Create(int weight);
}

public void AddFruit<T>( IFruitFactory<T> factory ) where T: BaseFruit {    
  BaseFruit fruit = factory.Create(weight); /*new Apple(150);*/    
  fruit.Enlist(fruitManager);
}

Yet another option is to use a functional approach. Pass in a factory method.

public void AddFruit<T>(Func<int,T> factoryDel) where T : BaseFruit { 
  BaseFruit fruit = factoryDel(weight); /* new Apple(150); */
  fruit.Enlist(fruitManager);
}
Yorke answered 8/4, 2009 at 19:32 Comment(3)
Good suggestion - although if you're not careful you can end up in the hell of the Java DOM API, with factories galore :(Blastopore
Yes, this is a solution I was concidering myself. But I was hoping for something in the line of constraints. Guess not then..Slaughterhouse
@boris, unfortunately the constraint language you are looking for does not exist at this point in timeYorke
T
13

You can do by using reflection:

public void AddFruit<T>()where T: BaseFruit
{
  ConstructorInfo constructor = typeof(T).GetConstructor(new Type[] { typeof(int) });
  if (constructor == null)
  {
    throw new InvalidOperationException("Type " + typeof(T).Name + " does not contain an appropriate constructor");
  }
  BaseFruit fruit = constructor.Invoke(new object[] { (int)150 }) as BaseFruit;
  fruit.Enlist(fruitManager);
}

EDIT: Added constructor == null check.

EDIT: A faster variant using a cache:

public void AddFruit<T>()where T: BaseFruit
{
  var constructor = FruitCompany<T>.constructor;
  if (constructor == null)
  {
    throw new InvalidOperationException("Type " + typeof(T).Name + " does not contain an appropriate constructor");
  }
  var fruit = constructor.Invoke(new object[] { (int)150 }) as BaseFruit;
  fruit.Enlist(fruitManager);
}
private static class FruitCompany<T>
{
  public static readonly ConstructorInfo constructor = typeof(T).GetConstructor(new Type[] { typeof(int) });
}
Tonetic answered 8/4, 2009 at 19:54 Comment(3)
Although I don't like the overhead of the reflection, as others have explained, this just is the way it is currently. Seeing how this constructor won't be called too much, I could go with this. Or the factory. Don't know yet.Slaughterhouse
This is currently my preferred approach because it does not add more complexity at the invocation side.Smutty
But now I've read about the Activator suggestion, which has similar nastiness as the above reflection solution, but with less lines of code :) I'm going to go for the Activator option.Smutty
S
11

As an addition to user1471935's suggestion:

To instantiate a generic class by using a constructor with one or more parameters, you can now use the Activator class.

T instance = Activator.CreateInstance(typeof(T), new object[] {...}) 

The list of objects are the parameters you want to supply. According to Microsoft:

CreateInstance [...] creates an instance of the specified type using the constructor that best matches the specified parameters.

There's also a generic version of CreateInstance (CreateInstance<T>()) but that one also does not allow you to supply constructor parameters.

Smutty answered 11/12, 2018 at 12:58 Comment(0)
I
2

I created this method:

public static V ConvertParentObjToChildObj<T,V> (T obj) where V : new()
{
    Type typeT = typeof(T);
    PropertyInfo[] propertiesT = typeT.GetProperties();
    V newV = new V();
    foreach (var propT in propertiesT)
    {
        var nomePropT = propT.Name;
        var valuePropT = propT.GetValue(obj, null);

        Type typeV = typeof(V);
        PropertyInfo[] propertiesV = typeV.GetProperties();
        foreach (var propV in propertiesV)
        {
            var nomePropV = propV.Name;
            if(nomePropT == nomePropV)
            {
                propV.SetValue(newV, valuePropT);
                break;
            }
        }
    }
    return newV;
}

I use that in this way:

public class A 
{
    public int PROP1 {get; set;}
}

public class B : A
{
    public int PROP2 {get; set;}
}

Code:

A instanceA = new A();
instanceA.PROP1 = 1;

B instanceB = new B();
instanceB = ConvertParentObjToChildObj<A,B>(instanceA);
Iterative answered 8/1, 2019 at 16:31 Comment(0)
J
1

You can use the following command:

 T instance = (T)typeof(T).GetConstructor(new Type[0]).Invoke(new object[0]);

Be sure to see the following reference.

Juta answered 5/10, 2020 at 18:46 Comment(1)
This is effectively what this answer does.Tullus
C
0

Recently I came across a very similar problem. Just wanted to share our solution with you all. I wanted to I created an instance of a Car<CarA> from a json object using which had an enum:

Dictionary<MyEnum, Type> mapper = new Dictionary<MyEnum, Type>();

mapper.Add(1, typeof(CarA));
mapper.Add(2, typeof(BarB)); 

public class Car<T> where T : class
{       
    public T Detail { get; set; }
    public Car(T data)
    {
       Detail = data;
    }
}
public class CarA
{  
    public int PropA { get; set; }
    public CarA(){}
}
public class CarB
{
    public int PropB { get; set; }
    public CarB(){}
}

var jsonObj = {"Type":"1","PropA":"10"}
MyEnum t = GetTypeOfCar(jsonObj);
Type objectT = mapper[t]
Type genericType = typeof(Car<>);
Type carTypeWithGenerics = genericType.MakeGenericType(objectT);
Activator.CreateInstance(carTypeWithGenerics , new Object[] { JsonConvert.DeserializeObject(jsonObj, objectT) });
Collie answered 25/5, 2016 at 16:3 Comment(0)
E
0

If you are willing to use a c# precompiler, you could resolve this so that it does have compile time constraints:

 // Used attribute
 [AttributeUsage(AttributeTargets.Parameter)]
 class ResolvedAsAttribute : Attribute
 {
    public string Expression;
    public ResolvedAsAttribute(string expression)
    {
        this.Expression = expression;
    }
 }

// Fruit manager source:
class FruitManager {

    ...

    public void AddFruit<TFruit>([ResolvedAs("(int p) => new TFruit(p)")] Func<int,TFruit> ctor = null)where TFruit: BaseFruit{
        BaseFruit fruit = ctor(weight); /*new Apple(150);*/
        fruit.Enlist(fruitManager);
    }
}

// Fruit user source:
#ResolveInclude ../Managers/FruitManager.cs
...
fruitManager.AddFruit<Apple>();
...

Your precompiler would then turn the Fruit user source into:

...
fruitManager.AddFruit<Apple>((int p) => new Apple(p));
...

Using Roslyn, your precompiler could look something like this (here is room for improvement):

    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Text;
    using Microsoft.CodeAnalysis;
    using Microsoft.CodeAnalysis.CSharp;
    using Microsoft.CodeAnalysis.CSharp.Syntax;
    using Microsoft.CodeAnalysis.CSharp.Symbols;
    using System.Threading;
    using System.Text.RegularExpressions;

    public class CsResolveIncludeAnalyser : CSharpSyntaxWalker
    {
        private List<(string key, MethodDeclarationSyntax node)> methodsToResolve = new List<(string key, MethodDeclarationSyntax node)>();
        public List<(string key, MethodDeclarationSyntax node)> Analyse(string source)
        {
            var tree = CSharpSyntaxTree.ParseText(source);
            var syntaxRoot = tree.GetRoot();
            Visit(tree.GetRoot());
            return methodsToResolve;
        }

        public override void VisitMethodDeclaration(MethodDeclarationSyntax methodDeclaration)
        {
            base.VisitMethodDeclaration(methodDeclaration);

            if (methodDeclaration.ParameterList.Parameters.Count > 0)
            {
                foreach (var parm in methodDeclaration.ParameterList.Parameters)
                {
                    var parmHasResolvedAs = parm.AttributeLists.Where((el) => el.Attributes.Where((attr) => attr.Name is IdentifierNameSyntax && ((IdentifierNameSyntax)attr.Name).Identifier.Text.Contains("ResolvedAs")).Any()).Any();
                    if (parmHasResolvedAs)
                    {
                        var name = methodDeclaration.Identifier.ValueText;
                        methodsToResolve.Add((name, methodDeclaration));
                        return;
                    }
                }
            }
        }
    }


    public class CsSwiftRewriter : CSharpSyntaxRewriter
    {
        private string currentFileName;
        private bool withWin32ErrorHandling;
        private Dictionary<string,MethodDeclarationSyntax> methodsToResolve = new Dictionary<string, MethodDeclarationSyntax>();

        private Dictionary<string, MethodDeclarationSyntax> getMethodsToResolve(string source, string fileName)
        {
            Dictionary<string, MethodDeclarationSyntax> methodsToResolve = new Dictionary<string, MethodDeclarationSyntax>();

            var path = Path.GetDirectoryName(fileName);
            var lines = source.Split(new[] { '\r', '\n' });
            var resolveIncludes = (from el in lines where el.StartsWith("#ResolveInclude") select el.Substring("#ResolveInclude".Length).Trim()).ToList();

            var analyser = new CsResolveIncludeAnalyser();
            foreach (var resolveInclude in resolveIncludes)
            {
                var src = File.ReadAllText(path + "/" + resolveInclude);
                var list = analyser.Analyse(src);
                foreach (var el in list)
                {
                    methodsToResolve.Add(el.key, el.node);
                }
            }

            return methodsToResolve;
        }
        public static string Convert(string source, string fileName)
        {
            return Convert(source, fileName, false);
        }

        public static string Convert(string source, string fileName, bool isWithWin32ErrorHandling)
        {

            var rewriter = new CsSwiftRewriter() { currentFileName = fileName, withWin32ErrorHandling = isWithWin32ErrorHandling };
            rewriter.methodsToResolve = rewriter.getMethodsToResolve(source, fileName);

            var resolveIncludeRegex = new Regex(@"(\#ResolveInclude)\b");
            source = resolveIncludeRegex.Replace(source, "//$1");

            var tree = CSharpSyntaxTree.ParseText(source);
            var syntaxRoot = tree.GetRoot();
            var result = rewriter.Visit(tree.GetRoot());
            return "#line 1 \"" + Path.GetFileName(fileName) + "\"\r\n" + result.ToFullString();
        }


        internal List<string> transformGenericArguments(List<string> arguments, GenericNameSyntax gName, TypeParameterListSyntax typeParameterList)
        {
            var res = new List<string>();
            var typeParameters = typeParameterList.ChildNodes().ToList();

            foreach (var argument in arguments)
            {
                var arg = argument;
                for (int i = 0; i < gName.TypeArgumentList.Arguments.Count; i++)
                {
                    var key = typeParameters[i];
                    var replacement = gName.TypeArgumentList.Arguments[i].ToString();
                    var regex = new System.Text.RegularExpressions.Regex($@"\b{key}\b");
                    arg = regex.Replace(arg, replacement);
                }
                res.Add(arg);
            }

            return res;
        }

        const string prefix = "";
        internal List<string> extractExtraArguments(MethodDeclarationSyntax methodDeclaration)
        {
            var res = new List<String>();

            foreach (var parm in methodDeclaration.ParameterList.Parameters)
            {
                foreach (var attrList in parm.AttributeLists)
                {
                    foreach (var attr in attrList.Attributes)
                    {
                        if (attr.Name is IdentifierNameSyntax && string.Compare(((IdentifierNameSyntax)attr.Name).Identifier.Text, "ResolvedAs") == 0)
                        {
                            var programmCode = attr.ArgumentList.Arguments.First().ToString().Trim();
                            var trimmedProgrammCode = (programmCode.Length >= 2 && programmCode[0] == '"' && programmCode[programmCode.Length - 1] == '"') ? programmCode.Substring(1, programmCode.Length - 2) : programmCode;
                            res.Add(prefix + parm.Identifier.Text + ":" + trimmedProgrammCode);
                        }
                    }
                }
            }
            return res;
        }

        internal List<string> extractExtraArguments(MethodDeclarationSyntax methodDeclaration, SimpleNameSyntax name)
        {
            var arguments = extractExtraArguments(methodDeclaration);
            if (name != null && name is GenericNameSyntax)
            {
                var gName = name as GenericNameSyntax;
                return transformGenericArguments(arguments, gName, methodDeclaration.TypeParameterList);
            }

            return arguments;
        }

        public override SyntaxNode VisitInvocationExpression(InvocationExpressionSyntax c_expressionStatement)
        {
            InvocationExpressionSyntax expressionStatement = (InvocationExpressionSyntax) base.VisitInvocationExpression(c_expressionStatement);

            List<string> addedArguments = null;
            switch (expressionStatement.Expression)
            {
                case MemberAccessExpressionSyntax exp:
                    if (methodsToResolve.ContainsKey(exp.Name?.Identifier.ValueText))
                    {
                        addedArguments = extractExtraArguments(methodsToResolve[exp.Name.Identifier.ValueText], exp.Name);
                    }
                    break;
                case GenericNameSyntax gName:
                    if (methodsToResolve.ContainsKey(gName.Identifier.ValueText))
                    {
                        addedArguments = extractExtraArguments(methodsToResolve[gName.Identifier.ValueText], gName);
                    }
                    break;
                default:
                    var name = (from el in expressionStatement.ChildNodes()
                                where el is GenericNameSyntax
                                select (el as GenericNameSyntax)).FirstOrDefault();
                    if (name != default(GenericNameSyntax))
                    {
                        if (methodsToResolve.ContainsKey(name.Identifier.ValueText))
                        {
                            addedArguments = extractExtraArguments(methodsToResolve[name.Identifier.ValueText], name);
                        }
                    }
                    break;
            }

            if (addedArguments?.Count > 0)
            {
                var addedArgumentsString = string.Join(",", addedArguments);
                var args = expressionStatement.ArgumentList.ToFullString();
                var paras = $"({(expressionStatement.ArgumentList.Arguments.Count > 0 ? string.Join(",", args.Substring(1,args.Length - 2), addedArgumentsString) : addedArgumentsString)})" ;
                var argList = SyntaxFactory.ParseArgumentList(paras);
                return expressionStatement.WithArgumentList(argList);
            }

            return expressionStatement;
        }
    }

The Precompiler could be called using a T4 script, optionally regenerating the source at compile time.

Ephram answered 22/4, 2021 at 14:3 Comment(0)
E
-2

It is still possible, with high performance, by doing the following:

    //
    public List<R> GetAllItems<R>() where R : IBaseRO, new() {
        var list = new List<R>();
        using ( var wl = new ReaderLock<T>( this ) ) {
            foreach ( var bo in this.items ) {
                T t = bo.Value.Data as T;
                R r = new R();
                r.Initialize( t );
                list.Add( r );
            }
        }
        return list;
    }

and

    //
///<summary>Base class for read-only objects</summary>
public partial interface IBaseRO  {
    void Initialize( IDTO dto );
    void Initialize( object value );
}

The relevant classes then have to derive from this interface and initialize accordingly. Please note, that in my case, this code is part of a surrounding class, which already has <T> as generic parameter. R, in my case, also is a read-only class. IMO, the public availability of Initialize() functions has no negative effect on the immutability. The user of this class could put another object in, but this would not modify the underlying collection.

Easterling answered 28/1, 2017 at 14:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.