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


409

목록에 추가 할 때 생성자를 통해 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.
   } 
   ...
}


2
이 기능을 언어로 제공하기위한 제안 : github.com/dotnet/roslyn/issues/2206
Ian Kemp

Microsoft 설명서에서 컴파일러 오류 CS0417을 참조하십시오 .
DavidRR

1
이 기능을 언어로 제공하는 제안은 다음으로 이동되었습니다. github.com/dotnet/csharplang/issues/769
활동 감소

답변:


410

함수에서 제네릭 형식의 인스턴스를 만들려면 "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));

제네릭 클래스에서 내부적으로 호출하면 어떻게 작동합니까? 아래 답변에 코드를 게시했습니다. 구체적인 클래스는 일반 클래스이므로 내부적으로 구체적인 클래스를 모릅니다. 이것에 대한 방법이 있습니까? 속성 초기화 구문을 사용하는 다른 제안을 사용하고 싶지 않습니다. 생성자에있는 논리를 무시할 것입니다.
ChrisCa

다른 질문에 내 코드를 추가했습니다 stackoverflow.com/questions/1682310/…
ChrisCa

21
이것은 현재 C #의 가장 성가신 한계 중 하나입니다. 클래스를 변경 불가능하게 만들고 싶습니다. 개인 설정 자만 있으면 클래스가 부작용으로 인해 유효하지 않은 상태가 될 수 없습니다. 나는 또한 그 Func과 람다를 사용하고 싶지만, 일반적으로 프로그래머가 아직 람다를 알지 못하기 때문에 그것이 bussiness 세계에서 여전히 문제가된다는 것을 알고 클래스가 이해하기 어렵습니다.
Tuomas Hietanen 2009

1
감사. 필자의 경우 메서드를 호출 할 때 생성자의 인수를 알고 있습니다. 매개 변수로 구성 할 수 없다는 Type 매개 변수의 한계를 극복해야했기 때문에 thunk 사용했습니다 . 썽 크는 메서드에 대한 선택적 매개 변수이며 제공된 경우에만 사용합니다. T result = thunk == null ? new T() : thunk(); 이 기능의 장점은 때로는 메서드 내부 및 외부에서 T생성하는 것이 아니라 한 곳에서 생성 논리를 통합하는 것 T입니다.
Carl G

나는 이것이 C # 언어가 프로그래머에게 거부하고 항상 그렇다고 말하는 것을 멈추기로 결정한 곳 중 하나라고 생각합니다! 이 접근 방식은 객체를 생성하는 약간 어색한 방법이지만 지금은 사용해야합니다.
AmirHossein Rezaei

331

.Net 3.5 및 activator 클래스를 사용한 후 :

(T)Activator.CreateInstance(typeof(T), args)

1
표현식 트리를 사용하여 객체를 만들 수도 있습니다
Welly Tambunan

4
인수 란 무엇입니까? 객체[]?
Rodney P. Barbati

3
예, args는 T의 생성자에 제공 할 값을 지정하는 object []입니다. "new object [] {par1, par2}"
TechNyquist


3
경고 : Activator.CreateInstance이 하나를 위해 전용 생성자가있는 경우 생성자가 전혀 사용되지 않는 것처럼 보이며 누군가가 "정리"하고 삭제하려고 시도 할 때 (런타임 오류가 발생합니다) 미래에 임의의 시간). 이 생성자를 사용하는 곳에 더미 함수를 추가하는 것이 좋습니다. 삭제하려고하면 컴파일 오류가 발생합니다.
jrh

51

아무도 '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 버전에서는 여전히 유용합니다.


내 이해는 대부분의 성능 저하가 처음에 ConstructorInfo를 얻는 것입니다. 프로파일 링하지 않고 내 말을 받아들이지 마십시오. 이 경우 나중에 재사용하기 위해 ConstructorInfo를 저장하면 리플렉션을 통해 반복되는 인스턴스화의 성능 저하를 완화 할 수 있습니다.
Kelsie

19
컴파일 타임 검사가 부족한 것이 더 우려의 원인이라고 생각합니다.
Dave Van den Eynde 2016 년

1
@James 동의합니다. "답변"으로 보지 않은 것에 놀랐습니다. 사실, 나는이 질문을 살펴본 후에 (당신과 같은) 좋은 쉬운 예를 찾을 것으로 기대했습니다. 어쨌든, 나에게서 +1이지만 Activator에서 +1도 대답합니다. Activator가하는 일을 조사한 결과, 매우 잘 설계된 반성임을 알 수 있습니다. :)
Mike

GetConstructor () 호출은 비싸므로 루프 전에 캐싱 할 가치가 있습니다. 이런 식으로 루프 내에서 Invoke () 만 호출하면 둘 다 호출하거나 Activator.CreateInstance ()를 사용하는 것보다 훨씬 빠릅니다.
Cosmin Rus

30

객체 이니셜 라이저

매개 변수가있는 생성자가 속성을 설정하는 것 외에 다른 작업을 수행하지 않으면 생성자를 호출하는 대신 객체 이니셜 라이저를 사용하여 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는 실행 속도가 가장 중요하고 다른 옵션을 유지 관리 할 수있는 경우 피하기 위해 약간의 성능 오버 헤드 를 가질 수 있습니다.


이렇게하면 T불변 T값 을 보호하지 못합니다 ( 종속성이 + 0 이상이거나 필요한 값이있는 경우 이제 T유효하지 않거나 사용할 수없는 상태 인 인스턴스를 만들 수 있습니다 . TDTO och viewmodel과 같이 죽은 것이 아니라면 이것을 피하십시오.
sara

20

아주 오래된 질문이지만 새로운 답변 ;-)

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 MyClass (T data) 생성자로 MyClass <T>를 생성하려는 경우 수행 할 작업에 대한 통찰력이 있습니까? 이 경우 Expression.Convert에서 예외가 발생하고 일반 제약 조건 기본 클래스를 사용하여 변환하면 생성자 정보가 일반 형식에 대한 것이므로 Expression.New가 발생합니다.
Mason

@Mason (;-대답하는 데 시간이 걸렸습니다) var myConstructor = CreateConstructor(typeof(MyClass<int>), typeof(int));이것은 잘 작동합니다. 모르겠어요
Jeroen van Langen

19

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

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에 의해 설명 된 방법입니다.


이것은 인수를 취하는 생성자에있는 논리를 우회 할 것입니다. 나는 Jared의 접근 방식과 같은 것을하고 싶지만 클래스 내에서 메소드를 내부적으로 호출하므로 구체적인 유형이 무엇인지 모릅니다 ... hmmm
ChrisCa

3
오른쪽은 T () 기본 생성자의 논리를 호출 한 다음 단순히 "Item"속성을 설정합니다. 기본이 아닌 생성자의 논리를 호출하려고하면 도움이되지 않습니다.
Scott Stafford

7

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

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

1
업데이트 : 올바른 오류 메시지는 다음과 같습니다. 'T': 변수
LB

빈 생성자를 사용하지 않기 때문에 객체의 인수를 전달합니다. 제네릭 형식에 new (object) 매개 변수가 있음을 지정하지 않고는 처리 할 수있는 방법이 없습니다.
Min

그런 다음 다음 중 하나를 수행해야합니다. 1. 리플렉션 사용 2. 생성자 대신 초기화 메서드에 매개 변수를 전달합니다. 여기서 초기화 메서드는 형식이 구현하고 where T :에 포함 된 인터페이스에 속합니다. ... 선언. 옵션 1은 나머지 코드에 가장 적은 영향을 주지만 옵션 2는 컴파일 시간 검사를 제공합니다.
Richard

반사를 사용하지 마십시오! 다른 답변에 요약 된 것과 동일한 다른 효과가있는 다른 방법이 있습니다.
Garry Shutler

@Garry-리플렉션이 반드시 최선의 접근 방법은 아니지만 나머지 코드베이스를 최소한으로 변경하여 필요한 것을 달성 할 수 있다는 데 동의합니다. 즉, @JaredPar의 팩토리 대리자 접근 방식을 선호합니다.
Richard

7

생성자 매개 변수를 사용하여 멤버 필드 또는 속성을 초기화하려는 경우 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. 
   }  
   ... 
} 

이것은 Garry Shutler가 말한 것과 동일하지만 추가 메모를하고 싶습니다.

물론 속성 값을 사용하여 필드 값을 설정하는 것보다 더 많은 일을 할 수 있습니다. "set ()"속성은 관련 필드를 설정하는 데 필요한 모든 처리와 객체를 사용하기 전에 전체 초기화가 수행되는지 여부를 확인하는 등의 기타 객체 자체에 필요한 처리를 트리거 할 수 있습니다. 예, 추악한 해결 방법이지만 M $의 새로운 () 제한을 극복합니다).

그것이 계획된 구멍인지 또는 우발적 인 부작용인지 확신 할 수 없지만 효과가 있습니다.

MS 사람들이 언어에 새로운 기능을 추가하고 전체 부작용 분석을 수행하지 않는 것 같습니다. 전체적인 것은 이것에 대한 좋은 증거입니다 ...


1
두 가지 제약 조건이 모두 필요합니다. InterfaceOrBaseClass는 컴파일러가 필드 / 속성 BaseMemberItem을 인식하도록합니다. "new ()"제약 조건이 주석 처리되면 오류가 발생합니다. 오류 6 new () 제약 조건이 없으므로 변수 유형 'T'의 인스턴스를 만들 수 없습니다.
fljx

내가 만난 상황은 여기에서 묻는 질문과 정확히 같지 않았지만이 대답은 내가 어디로 가야하는지 알았으며 잘 작동하는 것 같습니다.
RubyHaus

5
누군가 Microsoft를 "M $"이라고 언급 할 때마다 내 영혼의 작은 부분이 고통을받습니다.
Mathias Lykkegaard Lorenzen 2014 년

6

"매개 변수 T 유형의 인스턴스를 만들 때 인수를 제공 할 수 없습니다"라는 오류가 발생하여이를 수행해야한다는 것을 알았습니다.

var x = Activator.CreateInstance(typeof(T), args) as T;

5

사용할 수업에 액세스 할 수 있다면 내가 사용한이 방법을 사용할 수 있습니다.

대체 작성자가있는 인터페이스를 작성하십시오.

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

0

이것은 일종의 엉뚱한 일이며, 내가 엉뚱한 말을하면 반란을 의미 할 수 있지만 빈 생성자로 매개 변수가 지정된 유형을 제공 할 수 있다고 가정합니다.

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

인수를 사용하여 매개 변수화 된 유형에서 오브젝트를 효과적으로 구성 할 수 있습니다. 이 경우 원하는 생성자가 type의 단일 인수를 가지고 있다고 가정합니다 object. 제약이 허용 된 빈 생성자를 사용하여 T의 더미 인스턴스를 만든 다음 리플렉션을 사용하여 다른 생성자 중 하나를 가져옵니다.


0

때로는 속성 주입을 사용하는 답변과 유사한 접근 방식을 사용하지만 코드를 깨끗하게 유지합니다. 속성 집합을 가진 기본 클래스 / 인터페이스를 갖는 대신 "가난한 사람의 생성자"역할을하는 (가상) Initialize () 메서드 만 포함합니다. 그런 다음 생성자와 마찬가지로 각 클래스가 자체 초기화를 처리하도록 할 수 있으며 상속 체인을 처리하는 편리한 방법이 추가됩니다.

체인의 각 클래스가 고유 한 속성을 초기화하기를 원하는 상황에서 종종 자신을 찾으면 부모의 Initialize () 메서드를 호출하여 부모의 고유 속성을 초기화합니다. 이는 클래스가 다르지만 유사한 계층 구조 (예 : DTO와 맵핑되거나 비즈니스 오브젝트에 맵핑 된 비즈니스 오브젝트)가있는 경우에 특히 유용합니다.

초기화에 공통 사전을 사용하는 예 :

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

0

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

-4

새로운 생성자를 가진 객체 만 허용하려면 where 문으로 T를 제한해야한다고 생각합니다.

이제는 객체가없는 객체를 포함한 모든 것을 받아들입니다.


1
이 답변은 문맥에 맞지 않는 답변을 얻은 후에 질문으로 편집되었으므로이 답변을 변경할 수 있습니다.
shuttle87
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.