C #에서 메서드 내에서 전달 된 제네릭 형식을 인스턴스화하는 방법은 무엇입니까?


98

InstantiateType<T>아래 메서드 내에서 유형 T를 어떻게 인스턴스화 할 수 있습니까?

오류가 발생합니다. 'T'는 '유형 매개 변수'이지만 '변수'처럼 사용됩니다. :

(답변을 보려면 아래로 스크롤)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace TestGeneric33
{
    class Program
    {
        static void Main(string[] args)
        {
            Container container = new Container();
            Console.WriteLine(container.InstantiateType<Customer>("Jim", "Smith"));
            Console.WriteLine(container.InstantiateType<Employee>("Joe", "Thompson"));
            Console.ReadLine();
        }
    }

    public class Container
    {
        public T InstantiateType<T>(string firstName, string lastName) where T : IPerson
        {
            T obj = T();
            obj.FirstName(firstName);
            obj.LastName(lastName);
            return obj;
        }

    }

    public interface IPerson
    {
        string FirstName { get; set; }
        string LastName { get; set; }
    }

    public class Customer : IPerson
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Company { get; set; }
    }

    public class Employee : IPerson
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public int EmployeeNumber { get; set; }
    }
}

답변 :

모든 의견에 감사드립니다. 그들은 저를 올바른 방향으로 이끌었습니다. 이것이 제가하고 싶은 일입니다.

using System;

namespace TestGeneric33
{
    class Program
    {
        static void Main(string[] args)
        {
            Container container = new Container();
            Customer customer1 = container.InstantiateType<Customer>("Jim", "Smith");
            Employee employee1 = container.InstantiateType<Employee>("Joe", "Thompson");
            Console.WriteLine(PersonDisplayer.SimpleDisplay(customer1));
            Console.WriteLine(PersonDisplayer.SimpleDisplay(employee1));
            Console.ReadLine();
        }
    }

    public class Container
    {
        public T InstantiateType<T>(string firstName, string lastName) where T : IPerson, new()
        {
            T obj = new T();
            obj.FirstName = firstName;
            obj.LastName = lastName;
            return obj;
        }
    }

    public interface IPerson
    {
        string FirstName { get; set; }
        string LastName { get; set; }
    }

    public class PersonDisplayer
    {
        private IPerson _person;

        public PersonDisplayer(IPerson person)
        {
            _person = person;
        }

        public string SimpleDisplay()
        {
            return String.Format("{1}, {0}", _person.FirstName, _person.LastName);
        }

        public static string SimpleDisplay(IPerson person)
        {
            PersonDisplayer personDisplayer = new PersonDisplayer(person);
            return personDisplayer.SimpleDisplay();
        }
    }

    public class Customer : IPerson
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Company { get; set; }
    }

    public class Employee : IPerson
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public int EmployeeNumber { get; set; }
    }
}

더 나은 디자인 패턴으로 이동하려면 +1.
Joel Coehoorn

매우 깔끔하게 입력 된 코드의 경우 +1, 드문 경우입니다.
nawfal 2013

답변:


131

다음과 같이 방법을 선언하십시오.

public string InstantiateType<T>(string firstName, string lastName) 
              where T : IPerson, new()

끝에 추가 제약이 있습니다. 그런 다음 new메서드 본문에 인스턴스를 만듭니다 .

T obj = new T();    

4
나는 내 시대에 심각한 제네릭 타이핑 남용으로 수년 동안 C #을 작성해 왔으며, 제네릭 형식을 인스턴스화하기 위해 이와 같은 제약 조건을 정의 할 수 있다는 것을 결코 알지 못했습니다. 감사합니다!
Nicolas Martel

아주 아주 좋은!!
Sotiris Zegiannis

지정된 유형이 없으면 가능합니까?
jj

31

몇 가지 방법.

유형을 지정하지 않으면 생성자가 있어야합니다.

T obj = default(T); //which will produce null for reference types

생성자 사용 :

T obj = new T();

그러나 여기에는 다음 절이 필요합니다.

where T : new()

1
첫 번째는 참조 유형에 대한 인스턴스를 만드는 대신 null을 할당합니다.
Joel Coehoorn

1
네. 기본 생성자없이 형식을 만들려면 리플렉션을 사용해야합니다. default (T)는 모든 참조 형식에 대해 null입니다.
Dan C.

1
네, 절대적으로 완전성을 위해 포함되었습니다.
annakata

13

위의 답변을 확장하려면 where T:new()제네릭 메서드에 제약 조건을 추가 하려면 T에 매개 변수가없는 공용 생성자가 있어야합니다.

그것을 피하고 싶다면-그리고 팩토리 패턴에서 때때로 다른 사람들이 생성자를 직접 통과하지 않고 팩토리 메서드를 통과하도록 강제하는 경우-대안은 리플렉션 ( Activator.CreateInstance...) 을 사용 하고 기본 생성자를 비공개로 유지하는 것입니다. 그러나 이것은 물론 성능 저하를 동반합니다.


그것은 사람들이 :) "다른 모든 답변을"downvote 처음이 아니다
댄 C.

나는 dusgt가 질문을 정할 때까지 '경쟁력있는'답변을 찬성하지 않는 경우가 가끔 있음을 인정할 것입니다.
Ruben Bartelink

8

당신이 원하는 새로운 T를 (),하지만 당신은 추가해야합니다 , new()받는 where공장 방법에 대한 사양


내가, 내가 그것을 이해가 백업 충돌 도움, 여기 더 나은 설명보다는 게시 된 코드 같은 일반적인 사람들에 보인다
에드워드 Tanguay

감사합니다. 세상이 다시 말이됩니다!
Ruben Bartelink

수정하지만 대답은 틀림없이 짧은 쪽의 비트입니다)
로렌 소호 사우어에게

4

조금 오래되었지만 해결책을 찾는 다른 사람들에게는 아마도 이것이 흥미로울 수 있습니다 : http://daniel.wertheim.se/2011/12/29/c-generic-factory-with-support-for-private-constructors/

두 가지 솔루션. 하나는 Activator를 사용하고 다른 하나는 Compiled Lambda를 사용합니다.

//Person has private ctor
var person = Factory<Person>.Create(p => p.Name = "Daniel");

public static class Factory<T> where T : class 
{
    private static readonly Func<T> FactoryFn;

    static Factory()
    {
        //FactoryFn = CreateUsingActivator();

        FactoryFn = CreateUsingLambdas();
    }

    private static Func<T> CreateUsingActivator()
    {
        var type = typeof(T);

        Func<T> f = () => Activator.CreateInstance(type, true) as T;

        return f;
    }

    private static Func<T> CreateUsingLambdas()
    {
        var type = typeof(T);

        var ctor = type.GetConstructor(
            BindingFlags.Instance | BindingFlags.CreateInstance |
            BindingFlags.NonPublic,
            null, new Type[] { }, null);

        var ctorExpression = Expression.New(ctor);
        return Expression.Lambda<Func<T>>(ctorExpression).Compile();
    }

    public static T Create(Action<T> init)
    {
        var instance = FactoryFn();

        init(instance);

        return instance;
    }
}

2

리플렉션을 사용하여 개체의 생성자를 가져 와서 인스턴스화 할 수도 있습니다.

var c = typeof(T).GetConstructor();
T t = (T)c.Invoke();

1

팩토리 클래스를 사용하여 컴파일 된 람바 식으로 개체 빌드 : 제네릭 형식을 인스턴스화하는 가장 빠른 방법입니다.

public static class FactoryContructor<T>
{
    private static readonly Func<T> New =
        Expression.Lambda<Func<T>>(Expression.New(typeof (T))).Compile();

    public static T Create()
    {
        return New();
    }
}

벤치 마크를 설정하기 위해 수행 한 단계는 다음과 같습니다.

내 벤치 마크 테스트 방법을 만듭니다.

static void Benchmark(Action action, int iterationCount, string text)
{
    GC.Collect();
    var sw = new Stopwatch();
    action(); // Execute once before

    sw.Start();
    for (var i = 0; i <= iterationCount; i++)
    {
        action();
    }

    sw.Stop();
    System.Console.WriteLine(text + ", Elapsed: {0}ms", sw.ElapsedMilliseconds);
}

또한 공장 방법을 사용해 보았습니다.

public static T FactoryMethod<T>() where T : new()
{
    return new T();
}

테스트를 위해 가장 간단한 클래스를 만들었습니다.

public class A { }

테스트 할 스크립트 :

const int iterations = 1000000;
Benchmark(() => new A(), iterations, "new A()");
Benchmark(() => FactoryMethod<A>(), iterations, "FactoryMethod<A>()");
Benchmark(() => FactoryClass<A>.Create(), iterations, "FactoryClass<A>.Create()");
Benchmark(() => Activator.CreateInstance<A>(), iterations, "Activator.CreateInstance<A>()");
Benchmark(() => Activator.CreateInstance(typeof (A)), iterations, "Activator.CreateInstance(typeof (A))");

1,000,000 회 반복 결과 :

새로운 A () : 11ms

FactoryMethod A () : 275ms

FactoryClass A .Create () : 56ms

Activator.CreateInstance A () : 235ms

Activator.CreateInstance (typeof (A)) : 157ms

설명 : .NET Framework 4.5 및 4.6 (동등한 결과)을 사용하여 테스트했습니다 .


0

유형을 인스턴스화하는 함수를 만드는 대신

public T InstantiateType<T>(string firstName, string lastName) where T : IPerson, new()
    {
        T obj = new T();
        obj.FirstName = firstName;
        obj.LastName = lastName;
        return obj;
    }

이렇게 할 수 있었어

T obj = new T { FirstName = firstName, LastName = lastname };

1
이것은 질문에 대한 대답이 아닙니다. 여기서 진짜 문제는 제네릭 클래스의 새 인스턴스를 만들어야한다는 것입니다. 의도하지 않았을 수도 있지만 이니셜 라이저를 사용하면 원래 문제가 해결 될 것이라고 말하는 것처럼 보이지만 그렇지 않습니다. new()제약은 여전히 일에 대한 당신의 대답에 대한 제네릭 형식에 필요합니다.
User

이니셜 라이저가 여기에 유용한 도구라고 제안하고 도움을 주려는 경우 다른 답변이 아닌 댓글로 게시해야합니다.
사용자
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.