programing

템플릿 형식의 C# 일반 new()에 인수 전달

linuxpc 2023. 5. 26. 20:46
반응형

템플릿 형식의 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의 표현 방법(위 참조)을 사용한 데이터베이스 액세스 및 데이터 모델 클래스 채우기의 성능 테스트 및 데이터 모델 클래스의 직접 인스턴스화.

결론:표현 방법이 더 빠릅니다.

결과:

  1. 테스트: 데이터 모델 클래스의 직접 인스턴스:기록: 3558, 초: 1.2746019
  2. 테스트: 메서드 읽기 목록과 유형 매개 변수가 있는 인스턴스: 레코드: 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

반응형