생성자가 매개 변수를 필요로 하는 제네릭 형식의 인스턴스를 생성하시겠습니까?
한다면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
'sourcetip' 카테고리의 다른 글
정수만 사용하는 숫자 입력 유형? (0) | 2023.05.13 |
---|---|
유형 오류: re.findall()의 바이트와 같은 개체에 문자열 패턴을 사용할 수 없습니다. (0) | 2023.05.13 |
'--set-upstream'은 무엇을 합니까? (0) | 2023.05.13 |
권한 오류: Python의 [Errno 13] (0) | 2023.05.08 |
노드 멀티터 예기치 않은 필드 (0) | 2023.05.08 |