Создать экземпляр универсального типа?

Если BaseFruit имеет конструктор, который принимает int weightМогу ли я создать экземпляр фрукта в общем методе, как это?

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

Пример добавлен за комментариями. Кажется, я могу сделать это только если я дам BaseFruit конструктор без параметров, а затем заполнить все через переменные-члены. В моем реальном коде (не о фруктах) это довольно непрактично.

-Обновить-
Таким образом, кажется, что это никак не может быть решено ограничениями. Из ответов есть три варианта решения:

  • Фабричный образец
  • отражение
  • возбудитель

Я склонен думать, что рефлексия наименее чистая, но я не могу выбирать между двумя другими.

12 ответов

Решение

Дополнительно более простой пример:

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

Обратите внимание, что использование ограничения new() для T предназначено только для того, чтобы компилятор проверял открытый конструктор без параметров во время компиляции, фактический код, используемый для создания типа, - это класс Activator.

Вы должны будете убедиться в том, что конкретный конструктор уже существует, и такого рода требования могут быть запахом кода (или, скорее, чего-то, чего вам следует просто избегать в текущей версии на C#).

Вы не можете использовать любой параметризованный конструктор. Вы можете использовать конструктор без параметров, если у вас есть "where T : new()ограничение

Это боль, но такова жизнь:(

Это одна из вещей, которую я хотел бы рассмотреть с помощью "статических интерфейсов". Затем вы сможете ограничить T включением статических методов, операторов и конструкторов, а затем вызывать их.

Да; измените свое местоположение:

where T:BaseFruit, new()

Однако это работает только с конструкторами без параметров. У вас должны быть другие способы настройки вашего свойства (настройка самого свойства или чего-то подобного).

Самое простое решениеActivator.CreateInstance<T>()

Как указал Джон, это жизнь для ограничения беспараметрического конструктора. Однако другое решение заключается в использовании заводского шаблона. Это легко сдерживается

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

Еще один вариант - использовать функциональный подход. Пройдите в заводском методе.

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

Вы можете сделать с помощью отражения:

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

РЕДАКТИРОВАТЬ: Добавлен конструктор == проверка нуля.

РЕДАКТИРОВАТЬ: более быстрый вариант с использованием кэша:

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

В дополнение к предложению пользователя 1471935:

Чтобы создать экземпляр универсального класса с помощью конструктора с одним или несколькими параметрами, теперь вы можете использовать класс Activator.

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

Список объектов - это параметры, которые вы хотите указать. По словам Microsoft:

CreateInstance [...] создает экземпляр указанного типа, используя конструктор, который наилучшим образом соответствует указанным параметрам.

Существует также универсальная версия CreateInstance (CreateInstance<T>()) но это также не позволяет вам предоставлять параметры конструктора.

Я создал этот метод:

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

Я использую это таким образом:

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

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

Код:

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

B instanceB = new B();
instanceB = ConvertParentObjToChildObj<A,B>(instanceA);

Вы можете использовать следующую команду:

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

Обязательно посмотрите следующую ссылку.

Если вы хотите использовать прекомпилятор C #, вы можете решить эту проблему, чтобы у него были ограничения по времени компиляции:

      // 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>();
...

Затем ваш прекомпилятор превратит исходный код пользователя Fruit в:

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

При использовании Roslyn ваш прекомпилятор может выглядеть примерно так (здесь есть возможности для улучшения):

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

Прекомпилятор можно вызвать с помощью сценария T4, при необходимости регенерируя исходный код во время компиляции.

Недавно я столкнулся с очень похожей проблемой. Просто хотел поделиться нашим решением со всеми вами. Я хотел, чтобы я создал экземпляр Car<CarA> из объекта json, использующего перечисление:

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

Это все еще возможно, с высокой производительностью, делая следующее:

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

а также

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

Соответствующие классы затем должны наследоваться от этого интерфейса и соответственно инициализироваться. Обратите внимание, что в моем случае этот код является частью окружающего класса, который уже имеет в качестве универсального параметра. R, в моем случае, также является классом только для чтения. IMO, публичная доступность функций Initialize() не оказывает негативного влияния на неизменяемость. Пользователь этого класса может поместить другой объект, но это не изменит базовую коллекцию.

Другие вопросы по тегам