Create instance of generic type whose constructor requires a parameter?

C#.NetGenerics

C# Problem Overview


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.

C# Solutions


Solution 1 - C#

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

Solution 2 - C#

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.

Solution 3 - C#

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

Solution 4 - C#

Most simple solution Activator.CreateInstance<T>()

Solution 5 - C#

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

Solution 6 - C#

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

Solution 7 - C#

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.

Solution 8 - C#

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

Solution 9 - C#

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.

Solution 10 - C#

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

Solution 11 - C#

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.

Solution 12 - C#

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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionBoris CallensView Question on Stackoverflow
Solution 1 - C#meandmycodeView Answer on Stackoverflow
Solution 2 - C#Jon SkeetView Answer on Stackoverflow
Solution 3 - C#Adam RobinsonView Answer on Stackoverflow
Solution 4 - C#user1471935View Answer on Stackoverflow
Solution 5 - C#JaredParView Answer on Stackoverflow
Solution 6 - C#mmmmmmmmView Answer on Stackoverflow
Solution 7 - C#Rob VermeulenView Answer on Stackoverflow
Solution 8 - C#Diocleziano CarlettiView Answer on Stackoverflow
Solution 9 - C#محمد مهدی صباغیView Answer on Stackoverflow
Solution 10 - C#farshidView Answer on Stackoverflow
Solution 11 - C#Wolfgang GrinfeldView Answer on Stackoverflow
Solution 12 - C#cskwgView Answer on Stackoverflow