템플릿 형식의 C# 일반 new()에 인수 전달
목록에 추가할 때 생성자를 통해 T형 객체를 새로 생성하려고 합니다.
컴파일 오류가 발생합니다.오류 메시지는 다음과 같습니다.
'T': 변수의 인스턴스를 만들 때 인수를 제공할 수 없습니다.
하지만 우리 반에는 건설자 논쟁이 있어요!어떻게 하면 이 일을 해낼 수 있을까요?
public static string GetAllItems<T>(...) where T : new()
{
...
List<T> tabListItems = new List<T>();
foreach (ListItem listItem in listCollection)
{
tabListItems.Add(new T(listItem)); // error here.
}
...
}
함수에 일반 유형의 인스턴스를 만들려면 "새" 플래그로 제한해야 합니다.
public static string GetAllItems<T>(...) where T : new()
그러나 매개 변수가 없는 생성자를 호출하려는 경우에만 작동합니다.여기서는 그렇지 않습니다.대신 매개 변수를 기반으로 개체를 만들 수 있는 다른 매개 변수를 제공해야 합니다.가장 쉬운 것은 함수입니다.
public static string GetAllItems<T>(..., Func<ListItem,T> del) {
...
List<T> tabListItems = new List<T>();
foreach (ListItem listItem in listCollection)
{
tabListItems.Add(del(listItem));
}
...
}
그러면 그렇게 부를 수 있습니다.
GetAllItems<Foo>(..., l => new Foo(l));
.Net 3.5 이상에서 활성화 클래스를 사용할 수 있습니다.
(T)Activator.CreateInstance(typeof(T), args)
아무도 'Reflection' 답변(개인적으로 최고의 답변이라고 생각함)을 게시하지 않았기 때문에, 다음과 같습니다.
public static string GetAllItems<T>(...) where T : new()
{
...
List<T> tabListItems = new List<T>();
foreach (ListItem listItem in listCollection)
{
Type classType = typeof(T);
ConstructorInfo classConstructor = classType.GetConstructor(new Type[] { listItem.GetType() });
T classInstance = (T)classConstructor.Invoke(new object[] { listItem });
tabListItems.Add(classInstance);
}
...
}
편집: 이 답변은 .NET 3.5의 활성화 프로그램으로 인해 더 이상 사용되지 않습니다.CreateInstance는 이전 버전의 .NET에서 여전히 유용합니다.
아주 오래된 질문이지만, 새로운 대답 ;-)
더 익스프레션Tree version : (가장 빠르고 깨끗한 솔루션이라 생각합니다)
Welly Tambunan이 말했듯이, "우리는 표현 트리를 사용하여 객체를 만들 수도 있습니다."
그러면 지정된 유형/매개 변수에 대한 '생성자'(함수)가 생성됩니다.대리자를 반환하고 매개 변수 유형을 개체 배열로 수락합니다.
여기 있습니다.
// this delegate is just, so you don't have to pass an object array. _(params)_
public delegate object ConstructorDelegate(params object[] args);
public static ConstructorDelegate CreateConstructor(Type type, params Type[] parameters)
{
// Get the constructor info for these parameters
var constructorInfo = type.GetConstructor(parameters);
// define a object[] parameter
var paramExpr = Expression.Parameter(typeof(Object[]));
// To feed the constructor with the right parameters, we need to generate an array
// of parameters that will be read from the initialize object array argument.
var constructorParameters = parameters.Select((paramType, index) =>
// convert the object[index] to the right constructor parameter type.
Expression.Convert(
// read a value from the object[index]
Expression.ArrayAccess(
paramExpr,
Expression.Constant(index)),
paramType)).ToArray();
// just call the constructor.
var body = Expression.New(constructorInfo, constructorParameters);
var constructor = Expression.Lambda<ConstructorDelegate>(body, paramExpr);
return constructor.Compile();
}
내 클래스 예:
public class MyClass
{
public int TestInt { get; private set; }
public string TestString { get; private set; }
public MyClass(int testInt, string testString)
{
TestInt = testInt;
TestString = testString;
}
}
용도:
// you should cache this 'constructor'
var myConstructor = CreateConstructor(typeof(MyClass), typeof(int), typeof(string));
// Call the `myConstructor` function to create a new instance.
var myObject = myConstructor(10, "test message");
다른 예: 유형을 배열로 전달
var type = typeof(MyClass);
var args = new Type[] { typeof(int), typeof(string) };
// you should cache this 'constructor'
var myConstructor = CreateConstructor(type, args);
// Call the `myConstructor` fucntion to create a new instance.
var myObject = myConstructor(10, "test message");
식의 디버그 보기
.Lambda #Lambda1<TestExpressionConstructor.MainWindow+ConstructorDelegate>(System.Object[] $var1) {
.New TestExpressionConstructor.MainWindow+MyClass(
(System.Int32)$var1[0],
(System.String)$var1[1])
}
이는 생성되는 코드와 동일합니다.
public object myConstructor(object[] var1)
{
return new MyClass(
(System.Int32)var1[0],
(System.String)var1[1]);
}
작은 단점
모든 값 유형 매개 변수는 개체 배열처럼 전달될 때 상자로 표시됩니다.
간단한 성능 테스트:
private void TestActivator()
{
Stopwatch sw = Stopwatch.StartNew();
for (int i = 0; i < 1024 * 1024 * 10; i++)
{
var myObject = Activator.CreateInstance(typeof(MyClass), 10, "test message");
}
sw.Stop();
Trace.WriteLine("Activator: " + sw.Elapsed);
}
private void TestReflection()
{
var constructorInfo = typeof(MyClass).GetConstructor(new[] { typeof(int), typeof(string) });
Stopwatch sw = Stopwatch.StartNew();
for (int i = 0; i < 1024 * 1024 * 10; i++)
{
var myObject = constructorInfo.Invoke(new object[] { 10, "test message" });
}
sw.Stop();
Trace.WriteLine("Reflection: " + sw.Elapsed);
}
private void TestExpression()
{
var myConstructor = CreateConstructor(typeof(MyClass), typeof(int), typeof(string));
Stopwatch sw = Stopwatch.StartNew();
for (int i = 0; i < 1024 * 1024 * 10; i++)
{
var myObject = myConstructor(10, "test message");
}
sw.Stop();
Trace.WriteLine("Expression: " + sw.Elapsed);
}
TestActivator();
TestReflection();
TestExpression();
결과:
Activator: 00:00:13.8210732
Reflection: 00:00:05.2986945
Expression: 00:00:00.6681696
용사를 합니다.Expressions
실행 속도보다 +/- 8배 빠름ConstructorInfo
+/- 를 사용하는 것보다 20배 더 빠릅니다.Activator
개체 이니셜라이저
매개 변수가 있는 생성자가 속성 설정 외에 다른 작업을 수행하지 않는 경우 생성자를 호출하는 대신 객체 이니셜라이저를 사용하여 C#3 이상에서 이 작업을 수행할 수 있습니다(앞에서 설명한 것처럼 불가능함).
public static string GetAllItems<T>(...) where T : new()
{
...
List<T> tabListItems = new List<T>();
foreach (ListItem listItem in listCollection)
{
tabListItems.Add(new T() { YourPropertyName = listItem } ); // Now using object initializer
}
...
}
이를 사용하면 언제든지 기본(빈) 생성자에 생성자 로직을 넣을 수 있습니다.
액티베이터.인스턴스() 생성
또는 활성화 프로그램에 전화할 수 있습니다.다음과 같은 인스턴스() 생성:
public static string GetAllItems<T>(...) where T : new()
{
...
List<T> tabListItems = new List<T>();
foreach (ListItem listItem in listCollection)
{
object[] args = new object[] { listItem };
tabListItems.Add((T)Activator.CreateInstance(typeof(T), args)); // Now using Activator.CreateInstance
}
...
}
활성화 프로그램을 확인합니다.CreateInstance에는 실행 속도가 최우선이고 다른 옵션을 유지할 수 있는 경우 방지해야 할 몇 가지 성능 오버헤드가 있을 수 있습니다.
이것은 당신의 상황에서 작동하지 않을 것입니다.생성자가 비어 있다는 제약 조건만 지정할 수 있습니다.
public static string GetAllItems<T>(...) where T: new()
이 인터페이스를 정의하여 속성 주입을 사용할 수 있습니다.
public interface ITakesAListItem
{
ListItem Item { set; }
}
그런 다음 방법을 다음과 같이 변경할 수 있습니다.
public static string GetAllItems<T>(...) where T : ITakesAListItem, new()
{
...
List<T> tabListItems = new List<T>();
foreach (ListItem listItem in listCollection)
{
tabListItems.Add(new T() { Item = listItem });
}
...
}
은 다또대은안입니다.Func
방법은 JaredPar에 의해 설명되었습니다.
생성자 매개 변수를 사용하여 멤버 필드 또는 속성을 초기화하기만 하면 C# > = 3에서 매우 쉽게 할 수 있습니다.
public static string GetAllItems<T>(...) where T : InterfaceOrBaseClass, new()
{
...
List<T> tabListItems = new List<T>();
foreach (ListItem listItem in listCollection)
{
tabListItems.Add(new T{ BaseMemberItem = listItem }); // No error, BaseMemberItem owns to InterfaceOrBaseClass.
}
...
}
이것은 게리 셔틀러가 말한 것과 같은 것이지만, 저는 추가적인 메모를 하고 싶습니다.
물론 속성 트릭을 사용하여 필드 값을 설정하는 것 이상의 작업을 수행할 수 있습니다.속성 "set()"은 관련 필드를 설정하는 데 필요한 모든 처리와 객체가 사용되기 전에 전체 초기화가 수행되는지 확인하는 검사를 포함하여 객체 자체에 대한 다른 필요성을 트리거하여 전체 구성을 시뮬레이션할 수 있습니다(예, 보기 흉한 해결 방법이지만 M$의 새로운() 제한을 극복합니다).
계획된 구멍인지 우발적인 부작용인지 확신할 수 없지만 효과가 있습니다.
MS 사람들이 언어에 새로운 기능을 추가하고 완전한 부작용 분석을 하지 않는 것처럼 보이는 것은 매우 재미있습니다.모든 일반적인 것은 이것의 좋은 증거입니다.
컴파일러가 T가 기본 생성자를 제공하도록 보장하려면 T: new() 위치를 추가해야 합니다.
public static string GetAllItems<T>(...) where T: new()
"type 매개 변수 T의 인스턴스를 만들 때 인수를 제공할 수 없습니다"라는 오류가 발생하여 이 작업을 수행해야 했습니다.
var x = Activator.CreateInstance(typeof(T), args) as T;
사용할 클래스에 대한 액세스 권한이 있으면 제가 사용한 이 접근 방식을 사용할 수 있습니다.
대체 작성자가 있는 인터페이스를 만듭니다.
public interface ICreatable1Param
{
void PopulateInstance(object Param);
}
빈 작성자로 클래스를 만들고 다음 방법을 구현합니다.
public class MyClass : ICreatable1Param
{
public MyClass() { //do something or nothing }
public void PopulateInstance (object Param)
{
//populate the class here
}
}
이제 일반적인 방법을 사용합니다.
public void MyMethod<T>(...) where T : ICreatable1Param, new()
{
//do stuff
T newT = new T();
T.PopulateInstance(Param);
}
액세스 권한이 없는 경우 대상 클래스를 래핑합니다.
public class MyClass : ICreatable1Param
{
public WrappedClass WrappedInstance {get; private set; }
public MyClass() { //do something or nothing }
public void PopulateInstance (object Param)
{
WrappedInstance = new WrappedClass(Param);
}
}
C#11의 정적 추상 인터페이스 방법 기능을 사용하면 공장 패턴을 사용하여 해결 방법을 구축할 수 있습니다.예:
public interface IFactory<TSelf> where TSelf : IFactory<TSelf> {
static abstract TSelf New(int i, string s);
}
public struct Foo : IFactory<Foo> {
public static Foo New(int i, string s) {
return new Foo(i, s);
}
public readonly int I;
public readonly string S;
public Foo(int i, string s) {
I = i;
S = s;
}
}
public static class Maker {
public static T Make<T>(int i, string s) where T : IFactory<T> {
return T.New(i, s);
}
}
이 접근 방식의 한계는 대상 클래스에 특정 공장 인터페이스를 구현해야 하므로 사용자가 소유한 클래스에서만 사용할 수 있다는 것입니다.
이것은 좀 더럽고, 제가 약간 더럽다고 말할 때는 반항을 의미할 수도 있지만, 매개 변수화된 유형에 빈 생성자를 제공할 수 있다고 가정하면, 다음과 같습니다.
public static T GetTInstance<T>() where T: new()
{
var constructorTypeSignature = new Type[] {typeof (object)};
var constructorParameters = new object[] {"Create a T"};
return (T) new T().GetType().GetConstructor(constructorTypeSignature).Invoke(constructorParameters);
}
매개 변수화된 형식에서 인수를 사용하여 개체를 효과적으로 구성할 수 있습니다.이 경우 내가 원하는 생성자가 유형의 단일 인수를 가지고 있다고 가정합니다.object
우리는 허용된 제약 조건 빈 생성자를 사용하여 T의 더미 인스턴스를 만든 다음 반사를 사용하여 다른 생성자 중 하나를 얻습니다.
저는 때때로 속성 주입을 사용하여 답과 유사한 접근 방식을 사용하지만 코드를 더 깨끗하게 유지합니다.속성 집합이 있는 기본 클래스/인터페이스 대신 (가상)만 포함됩니다.초기화() - "가난한 사람의 생성자" 역할을 하는 메서드입니다.그런 다음 각 클래스가 생성자와 마찬가지로 자체 초기화를 처리하도록 할 수 있으며, 상속 체인을 처리하는 편리한 방법도 추가됩니다.
체인의 각 클래스가 고유 속성을 초기화한 다음 상위의 초기화() 메서드를 호출하여 상위의 고유 속성을 초기화하는 경우가 많습니다.이 기능은 DTO:s에 매핑된 비즈니스 개체와 같이 클래스가 다르지만 계층이 유사한 경우에 특히 유용합니다.
초기화를 위해 공통 사전을 사용하는 예제:
void Main()
{
var values = new Dictionary<string, int> { { "BaseValue", 1 }, { "DerivedValue", 2 } };
Console.WriteLine(CreateObject<Base>(values).ToString());
Console.WriteLine(CreateObject<Derived>(values).ToString());
}
public T CreateObject<T>(IDictionary<string, int> values)
where T : Base, new()
{
var obj = new T();
obj.Initialize(values);
return obj;
}
public class Base
{
public int BaseValue { get; set; }
public virtual void Initialize(IDictionary<string, int> values)
{
BaseValue = values["BaseValue"];
}
public override string ToString()
{
return "BaseValue = " + BaseValue;
}
}
public class Derived : Base
{
public int DerivedValue { get; set; }
public override void Initialize(IDictionary<string, int> values)
{
base.Initialize(values);
DerivedValue = values["DerivedValue"];
}
public override string ToString()
{
return base.ToString() + ", DerivedValue = " + DerivedValue;
}
}
ListItem에서 T형으로 변환하기만 하면 되는 경우 T 클래스에서 변환 연산자로 이 변환을 구현할 수 있습니다.
public class T
{
public static implicit operator T(ListItem listItem) => /* ... */;
}
public static string GetAllItems(...)
{
...
List<T> tabListItems = new List<T>();
foreach (ListItem listItem in listCollection)
{
tabListItems.Add(listItem);
}
...
}
부가실적정보
Jeroen van Langen의 표현 방법(위 참조)을 사용한 데이터베이스 액세스 및 데이터 모델 클래스 채우기의 성능 테스트 및 데이터 모델 클래스의 직접 인스턴스화.
결론:표현 방법이 더 빠릅니다.
결과:
- 테스트: 데이터 모델 클래스의 직접 인스턴스:기록: 3558, 초: 1.2746019
- 테스트: 메서드 읽기 목록과 유형 매개 변수가 있는 인스턴스: 레코드: 3558, 초: 0.4878858
식 방법의 코드 예제:
var list = ReadList<DataModel>(SQLStatement, Connection);
메서드 읽기 목록: 참고: 모든 데이터 모델 클래스에는 매개 변수 유형이 SQLDataReader인 생성자가 있습니다.
public static List<pDataModel> ReadList<pDataModel>(string pSQLStatement, SqlConnection pConnection) where pDataModel : new()
{
// constructor of data model
var lType = typeof(pDataModel);
var lParameters = new Type[] { typeof(SqlDataReader) };
var lDataModelConstructor = CreateConstructor(lType, lParameters);
// read data
List<pDataModel> lDataList = new List<pDataModel>();
using (pConnection)
{
SqlCommand lCommand;
lCommand = new SqlCommand(pSQLStatement, pConnection);
pConnection.Open();
SqlDataReader lReader = lCommand.ExecuteReader();
if (lReader.HasRows)
{
while (lReader.Read())
{
pDataModel lDataItem = (pDataModel)lDataModelConstructor(lReader);
lDataList.Add(lDataItem);
}
}
lReader.Close();
pConnection.Close();
}
return lDataList;
}
직접 인스턴스화의 코드 예:
List<DataModel> list= new List<DataModel>();
using (connection)
{
SqlCommand command;
command = new SqlCommand(SQLStatement, connection);
connection.Open();
SqlDataReader reader = command.ExecuteReader();
if (reader.HasRows)
{
while (reader.Read())
{
list.Add(new DataModel(reader));
}
}
reader.Close();
connection.Close();
}
새 생성자가 있는 개체만 허용하려면 where 문으로 T를 제한해야 한다고 생각합니다.
현재 그것은 그것이 없는 물체를 포함한 모든 것을 받아들입니다.
언급URL : https://stackoverflow.com/questions/840261/passing-arguments-to-c-sharp-generic-new-of-templated-type
'programing' 카테고리의 다른 글
Xcode에서 블록 댓글을 작성할 수 있는 바로가기가 있습니까? (0) | 2023.05.26 |
---|---|
Visual Studio(C#, VB)에서 "32비트 선호" 컴파일러 플래그는 무엇을 의미합니까? (0) | 2023.05.26 |
포트 80(Ubuntu/Linode)에서 Node.js를 실행하는 경우의 모범 사례 (0) | 2023.05.26 |
SQL Server 2008 R2에서 CONCAT 기능을 사용하려면 어떻게 해야 합니까? (0) | 2023.05.26 |
Node.js에서 기본 HTML 로드 (0) | 2023.05.26 |