sourcetip

생성자가 매개 변수를 필요로 하는 제네릭 형식의 인스턴스를 생성하시겠습니까?

fileupload 2023. 5. 13. 10:42
반응형

생성자가 매개 변수를 필요로 하는 제네릭 형식의 인스턴스를 생성하시겠습니까?

한다면BaseFruit를 받아들이는 생성자가 있습니다.int weight이렇게 일반적인 방법으로 과일 한 조각을 인스턴스화할 수 있습니까?

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

설명 뒤에 예제가 추가됩니다.제가 이걸 줄 수 있을 것 같아요.BaseFruit매개 변수가 없는 생성자를 선택한 다음 멤버 변수를 통해 모든 항목을 채웁니다.제 진짜 코드에서는 (과일에 관한 것이 아니라) 이것은 다소 비현실적입니다.

-업데이트-
그래서 어떤 식으로든 제약조건으로는 해결될 수 없는 것 같습니다.답안에는 세 가지 후보 솔루션이 있습니다.

  • 공장 패턴
  • 반사
  • 액티베이터

저는 반성이 가장 덜 깨끗한 것이라고 생각하는 경향이 있는데, 나머지 두 가지 중 어느 것을 결정할 수 없습니다.

더 간단한 예:

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

T에서 new() 제약 조건을 사용하는 것은 컴파일러가 컴파일 시 매개 변수가 없는 공용 생성자를 확인하도록 하기 위한 것이며, 유형을 만드는 데 사용되는 실제 코드는 Activator 클래스입니다.

존재하는 특정 생성자에 대해 자신을 확인해야 하며, 이러한 요구 사항은 코드 냄새일 수 있습니다(또는 c#의 현재 버전에서는 피해야 할 것입니다).

매개 변수화된 생성자를 사용할 수 없습니다."가 있는 경우 매개 변수 없는 생성자를 사용할 수 있습니다.where T : new()속박

괴로운 일이지만, 인생은 그런 것입니다 :)

이것은 제가 "정적 인터페이스"로 다루고 싶은 것 중 하나입니다.그러면 정적 메소드, 연산자 및 생성자를 포함하도록 T를 제한한 다음 이들을 호출할 수 있습니다.

예, 위치를 변경합니다.

where T:BaseFruit, new()

그러나 이것은 매개 변수가 없는 생성자에서만 작동합니다.속성을 설정하는 다른 방법(속성 자체 또는 유사한 방법)이 있어야 합니다.

가장 간단한 솔루션Activator.CreateInstance<T>()

Jon이 지적했듯이 이것은 매개변수가 없는 생성자를 구속하는 삶입니다.그러나 다른 해결책은 공장 패턴을 사용하는 것입니다.이것은 쉽게 구속할 수 있습니다.

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

편집: 생성자 == null 검사를 추가했습니다.

편집: 캐시를 사용하는 더 빠른 변형:

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의 제안에 추가로:

하나 이상의 매개 변수가 있는 생성자를 사용하여 일반 클래스를 인스턴스화하려면 이제 활성화 프로그램 클래스를 사용할 수 있습니다.

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

다음 참조를 참조하십시오.

최근에 저는 매우 유사한 문제를 발견했습니다.저희 솔루션을 여러분과 공유하고 싶었습니다.는 저는제만예들면의 .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) });

c# 프리컴파일러를 사용할 의향이 있다면 컴파일 시간 제약이 없도록 이 문제를 해결할 수 있습니다.

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

그런 다음 프리컴파일러는 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 스크립트를 사용하여 호출할 수 있으며, 선택적으로 컴파일 시간에 소스를 재생성할 수 있습니다.

고성능의 경우에도 다음 작업을 수행하면 가능합니다.

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

그런 다음 관련 클래스가 이 인터페이스에서 파생되고 그에 따라 초기화되어야 합니다.제 경우, 이 코드는 이미 <T>를 제네릭 매개 변수로 가지고 있는 주변 클래스의 일부입니다.제 경우에도 R은 읽기 전용 수업입니다.IMO, Initialize() 함수의 공개 가용성은 불변성에 부정적인 영향을 미치지 않습니다.이 클래스의 사용자는 다른 개체를 넣을 수 있지만 기본 컬렉션은 수정되지 않습니다.

언급URL : https://stackoverflow.com/questions/731452/create-instance-of-generic-type-whose-constructor-requires-a-parameter

반응형