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.