[C#] 템플릿 형식의 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.
   }
   ...
}



답변

함수에서 제네릭 형식의 인스턴스를 만들려면 “new”플래그로 제한해야합니다.

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 및 activator 클래스를 사용한 후 :

(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의 Activator.CreateInstance로 인해 더 이상 사용되지 않지만 이전 .NET 버전에서는 여전히 유용합니다.


답변

객체 이니셜 라이저

매개 변수가있는 생성자가 속성을 설정하는 것 외에 다른 작업을 수행하지 않으면 생성자를 호출하는 대신 객체 이니셜 라이저를 사용하여 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
   }
   ...
}

이를 사용하면 생성자 논리를 항상 기본 (빈) 생성자에 넣을 수 있습니다.

Activator.CreateInstance ()

또는 Activator.CreateInstance ()를 다음 과 같이 호출 할 수 있습니다 .

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

Activator.CreateInstance는 실행 속도가 가장 중요하고 다른 옵션을 유지 관리 할 수있는 경우 피하기 위해 약간의 성능 오버 헤드 를 가질 수 있습니다.


답변

아주 오래된 질문이지만 새로운 답변 😉

ExpressionTree 버전 : (가장 빠르고 깨끗한 솔루션이라고 생각합니다)

마찬가지로 웰리 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();
}

MyClass 예 :

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

식의 DebugView

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

작은 단점

모든 valuetypes 매개 변수는 객체 배열처럼 전달 될 때 상자로 표시됩니다.


간단한 성능 테스트 :

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


답변

상황에 따라 작동하지 않습니다. 빈 생성자가있는 제약 조건 만 지정할 수 있습니다.

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

다른 대안은 FuncJaredPar에 의해 설명 된 방법입니다.


답변

T가 기본 생성자를 제공한다는 것을 컴파일러에 알리려면 T : new ()를 추가해야합니다.

public static string GetAllItems<T>(...) where T: new()