생성자에 매개 변수가 필요한 일반 유형의 인스턴스를 작성 하시겠습니까?


230

BaseFruit을 허용하는 생성자가있는 경우 int weight이와 같은 일반적인 방법으로 과일 조각을 인스턴스화 할 수 있습니까?

public void AddFruit<T>()where T: BaseFruit{
    BaseFruit fruit = new T(weight); /*new Apple(150);*/
    fruit.Enlist(fruitManager);
}

주석 뒤에 예제가 추가됩니다. BaseFruit매개 변수가없는 생성자를 제공 한 다음 멤버 변수를 통해 모든 것을 채우는 경우에만이 작업을 수행 할 수 있습니다 . 내 실제 코드 (과일에 관한 것이 아님)에서 이것은 오히려 비실용적입니다.

-Update-
그래서 어떤 식 으로든 제약으로 해결할 수없는 것 같습니다. 답변에서 세 가지 후보 솔루션이 있습니다.

  • 공장 패턴
  • 반사
  • 활성제

나는 반사가 가장 깨끗하지 않다고 생각하는 경향이 있지만 다른 두 가지를 결정할 수는 없습니다.


1
BTW : 오늘 저는 선택한 IoC 라이브러리를 사용하여이 문제를 해결할 것입니다.
보리스 칼 렌스

리플렉션과 액티베이터는 실제로 밀접한 관련이 있습니다.
Rob Vermeulen

답변:


335

또한 더 간단한 예 :

return (T)Activator.CreateInstance(typeof(T), new object[] { weight });

T에서 new () 제약 조건을 사용하는 것은 컴파일 타임에 컴파일러가 공용 매개 변수없는 생성자를 확인하도록하기위한 것이므로 형식을 만드는 데 사용되는 실제 코드는 Activator 클래스입니다.

존재하는 특정 생성자에 대해 스스로를 확인해야하며 이러한 종류의 요구 사항은 코드 냄새 일 수 있습니다 (또는 오히려 C #의 현재 버전에서는 피해야 할 것).


이 생성자는 기본 클래스 (BaseFruit)에 있기 때문에 생성자가있을 것입니다. 그러나 실제로 언젠가 basefruit가 더 많은 매개 변수가 필요하다고 결정하면 망할 수 있습니다. 그래도 ACtivator 클래스를 살펴 봅니다. 전에는 듣지 못했습니다.
Boris Callens

3
이것은 잘 작동했습니다. CreateInstance <T> () 프로시 저도 있지만 일부 rason에 대한 매개 변수에 대한 과부하는 없습니다.
Boris Callens

20
사용할 필요가 없습니다 new object[] { weight }. CreateInstance는 params로 선언 public static object CreateInstance(Type type, params object[] args)되므로 할 수 있습니다 return (T) Activator.CreateInstance(typeof(T), weight);. 여러 개의 매개 변수가있는 경우 별도의 인수로 전달하십시오. 이미 열거 가능한 매개 변수로 구성된 매개 변수가있는 경우에만 매개 변수로 변환하여 object[]전달해야합니다 CreateInstance.
ErikE

2
내가 읽은 성능 문제가 있습니다. 대신 컴파일 된 람다를 사용하십시오. vagifabilov.wordpress.com/2010/04/02/…
David

1
@RobVermeulen-각 Fruit 클래스의 정적 속성과 같은 것으로 생각 Func합니다. 여기에는 새 인스턴스를 만드는가 포함되어 있습니다 . Apple생성자 사용법이 이라고 가정하십시오 new Apple(wgt). 그런 다음에 추가 Apple수업이 정의를 : static Func<float, Fruit> CreateOne { get; } = (wgt) => new Apple(wgt);공장 정의 public static Fruit CreateFruitGiven(float weight, Func<float, Fruit> createOne) { return createOne(weight); } 사용법 : Factory.CreateFruit(57.3f, Apple.CreateOne);- 생성과를 반환 Apple과 함께 weight=57.3f.
ToolmakerSteve

92

매개 변수화 된 생성자를 사용할 수 없습니다. " where T : new()"제약 조건 이있는 경우 매개 변수가없는 생성자를 사용할 수 있습니다 .

고통이지만 그런 삶입니다 :(

이것이 내가 "정적 인터페이스" 로 해결하고 싶은 것 중 하나입니다 . 그런 다음 정적 메소드, 연산자 및 생성자를 포함하도록 T를 제한 한 다음 호출 할 수 있습니다.


2
적어도 당신은 그러한 제약을 할 수 있습니다-Java는 항상 나를 실망시킵니다.
Marcel Jackwerth 2009

@ JonSkeet : VB6.0에서 호출 할 API를 .NET generic으로 노출하면 여전히 작동합니까?
Roy Lee

@Roylee : 잘 모르겠지만 의심하지는 않습니다.
Jon Skeet

정적 인터페이스는 런타임을 변경하지 않고 언어 컴파일러에 의해 추가 될 수 있다고 생각하지만 언어 팀이 특정 사항을 조정하는 것이 좋습니다. 정적 인터페이스를 구현한다고 주장하는 모든 클래스에는 고유 한 유형의 정적 싱글 톤 인스턴스를 정의하는 특정 인터페이스 관련 이름의 중첩 클래스가 포함되어야합니다. 인터페이스와 연관된 인스턴스 필드가있는 정적 제네릭 형식으로, Reflection을 통해 한 번만 싱글 톤으로로드해야하지만 그 후에 바로 사용할 수 있습니다.
supercat

매개 변수화 된 생성자 제약 조건은 팩토리 메소드와 리턴 유형에 대한 일반 매개 변수를 사용하여 거의 같은 방식으로 처리 될 수 있습니다. 어떤 경우에도 그러한 기능을 지원하지 않는 언어로 작성된 코드가 적절한 정적 유형을 정의하지 않고 인터페이스를 구현한다고 주장하는 것을 막을 수는 없으므로 그러한 언어를 사용하여 작성된 코드는 런타임에 실패 할 수 있지만 사용자는 리플렉션을 피할 수 있습니다 암호.
supercat

61

예; 위치를 변경하십시오.

where T:BaseFruit, new()

그러나 이것은 매개 변수가없는 생성자 에서만 작동합니다 . 속성을 설정하는 다른 방법이 있어야합니다 (속성 자체 또는 유사한 설정).


생성자에 매개 변수가 없으면 이것이 안전 해 보입니다.
PerpetualStudent

당신은 내 생명을 구했습니다. T를 class 및 new () 키워드로 제한 할 수 없었습니다.
Genotypek

28

가장 간단한 솔루션 Activator.CreateInstance<T>()


1
제안 주셔서 감사합니다, 내가 필요한 곳에 도착했습니다. 이것은 매개 변수화 된 생성자를 사용할 수는 없지만. 그러나 제네릭이 아닌 변형 인 Activator.CreateInstance (typeof (T), new object [] {...})를 사용할 수 있습니다. 여기서 객체 배열에는 생성자에 대한 인수가 포함됩니다.
Rob Vermeulen

19

Jon이 지적했듯이 이것은 매개 변수가없는 생성자를 제한하는 삶입니다. 그러나 다른 해결책은 팩토리 패턴을 사용하는 것입니다. 이것은 쉽게 구속 할 수 있습니다

interface IFruitFactory<T> where T : BaseFruit {
  T Create(int weight);
}

public void AddFruit<T>( IFruitFactory<T> factory ) where T: BaseFruit {    
  BaseFruit fruit = factory.Create(weight); /*new Apple(150);*/    
  fruit.Enlist(fruitManager);
}

또 다른 옵션은 기능적 접근 방식을 사용하는 것입니다. 팩토리 방식으로 전달하십시오.

public void AddFruit<T>(Func<int,T> factoryDel) where T : BaseFruit { 
  BaseFruit fruit = factoryDel(weight); /* new Apple(150); */
  fruit.Enlist(fruitManager);
}

2
좋은 제안-조심하지 않으면 공장이 많은 Java DOM API를 대체 할 수 있습니다 :(
Jon Skeet

그렇습니다. 이것은 내가 스스로 결정한 해결책입니다. 그러나 나는 제약 조건에서 무언가를 기대하고있었습니다. 그때 생각하지 ..
보리스 칼 렌스

@boris, 불행히도 당신이 찾고있는 구속 조건 언어는 현재 존재하지 않습니다
JaredPar

11

리플렉션을 사용하여 수행 할 수 있습니다.

public void AddFruit<T>()where T: BaseFruit
{
  ConstructorInfo constructor = typeof(T).GetConstructor(new Type[] { typeof(int) });
  if (constructor == null)
  {
    throw new InvalidOperationException("Type " + typeof(T).Name + " does not contain an appropriate constructor");
  }
  BaseFruit fruit = constructor.Invoke(new object[] { (int)150 }) as BaseFruit;
  fruit.Enlist(fruitManager);
}

편집 : 생성자 == null 검사가 추가되었습니다.

편집 : 캐시를 사용하는 더 빠른 변형 :

public void AddFruit<T>()where T: BaseFruit
{
  var constructor = FruitCompany<T>.constructor;
  if (constructor == null)
  {
    throw new InvalidOperationException("Type " + typeof(T).Name + " does not contain an appropriate constructor");
  }
  var fruit = constructor.Invoke(new object[] { (int)150 }) as BaseFruit;
  fruit.Enlist(fruitManager);
}
private static class FruitCompany<T>
{
  public static readonly ConstructorInfo constructor = typeof(T).GetConstructor(new Type[] { typeof(int) });
}

다른 사람들이 설명했듯이 반사의 오버 헤드가 마음에 들지는 않지만 이것이 현재의 방식입니다. 이 생성자가 너무 많이 호출되지 않는 방법을 살펴보면 이것으로 갈 수 있습니다. 아니면 공장. 아직 몰라
Boris Callens

이것은 호출 측에서 더 많은 복잡성을 추가하지 않기 때문에 현재 선호되는 접근 방식입니다.
Rob Vermeulen

그러나 이제는 위의 리플렉션 솔루션과 비슷한 이름을 가진 Activator 제안에 대해 읽었지만 코드 줄이 더 적습니다.) Activator 옵션으로 이동합니다.
Rob Vermeulen

1

user1471935의 제안에 추가하여 :

하나 이상의 매개 변수가있는 생성자를 사용하여 일반 클래스를 인스턴스화하려면 Activator 클래스를 사용할 수 있습니다.

T instance = Activator.CreateInstance(typeof(T), new object[] {...}) 

개체 목록은 제공하려는 매개 변수입니다. Microsoft에 따르면 :

CreateInstance [...]는 지정된 매개 변수와 가장 일치하는 생성자를 사용하여 지정된 유형의 인스턴스를 작성합니다.

CreateInstance ( CreateInstance<T>()) 의 일반 버전도 있지만 생성자 매개 변수를 제공 할 수 없습니다.


1

이 방법을 만들었습니다.

public static V ConvertParentObjToChildObj<T,V> (T obj) where V : new()
{
    Type typeT = typeof(T);
    PropertyInfo[] propertiesT = typeT.GetProperties();
    V newV = new V();
    foreach (var propT in propertiesT)
    {
        var nomePropT = propT.Name;
        var valuePropT = propT.GetValue(obj, null);

        Type typeV = typeof(V);
        PropertyInfo[] propertiesV = typeV.GetProperties();
        foreach (var propV in propertiesV)
        {
            var nomePropV = propV.Name;
            if(nomePropT == nomePropV)
            {
                propV.SetValue(newV, valuePropT);
                break;
            }
        }
    }
    return newV;
}

나는 이런 식으로 사용합니다 :

public class A 
{
    public int PROP1 {get; set;}
}

public class B : A
{
    public int PROP2 {get; set;}
}

암호:

A instanceA = new A();
instanceA.PROP1 = 1;

B instanceB = new B();
instanceB = ConvertParentObjToChildObj<A,B>(instanceA);

0

최근에 비슷한 문제가 발생했습니다. 우리 솔루션을 모두와 공유하고 싶었습니다. Car<CarA>열거 형이있는 json 객체에서 인스턴스를 만들고 싶었습니다 .

Dictionary<MyEnum, Type> mapper = new Dictionary<MyEnum, Type>();

mapper.Add(1, typeof(CarA));
mapper.Add(2, typeof(BarB)); 

public class Car<T> where T : class
{       
    public T Detail { get; set; }
    public Car(T data)
    {
       Detail = data;
    }
}
public class CarA
{  
    public int PropA { get; set; }
    public CarA(){}
}
public class CarB
{
    public int PropB { get; set; }
    public CarB(){}
}

var jsonObj = {"Type":"1","PropA":"10"}
MyEnum t = GetTypeOfCar(jsonObj);
Type objectT = mapper[t]
Type genericType = typeof(Car<>);
Type carTypeWithGenerics = genericType.MakeGenericType(objectT);
Activator.CreateInstance(carTypeWithGenerics , new Object[] { JsonConvert.DeserializeObject(jsonObj, objectT) });

-2

다음을 수행하면 여전히 고성능으로 가능합니다.

    //
    public List<R> GetAllItems<R>() where R : IBaseRO, new() {
        var list = new List<R>();
        using ( var wl = new ReaderLock<T>( this ) ) {
            foreach ( var bo in this.items ) {
                T t = bo.Value.Data as T;
                R r = new R();
                r.Initialize( t );
                list.Add( r );
            }
        }
        return list;
    }

    //
///<summary>Base class for read-only objects</summary>
public partial interface IBaseRO  {
    void Initialize( IDTO dto );
    void Initialize( object value );
}

그런 다음 관련 클래스가이 인터페이스에서 파생되고 그에 따라 초기화되어야합니다. 필자의 경우이 코드는 주변 클래스의 일부이며 이미 일반 매개 변수로 <T>가 있습니다. 내 경우에는 R도 읽기 전용 클래스입니다. Initialize () 함수의 공개 가용성 인 IMO는 불변성에 부정적인 영향을 미치지 않습니다. 이 클래스의 사용자는 다른 객체를 넣을 수 있지만 기본 컬렉션을 수정하지는 않습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.