2013-02-12 2 views
2

generics를 사용하여 공장/빌더를 제공하여 인터페이스 구현을 추상화하려고합니다. 그러나, 나는 여러 디스패치와 실행 시간에 C# generics와 관련하여 이상하게 보이는 것을 실행하는 문제에 직면하고 있습니다.Generics를 사용한 다중 발송

기본 시나리오는 내가 정의한되는 여러 인터페이스 :

public class Factory 
{ 
    public T BuildModel<T>() 
    { 
     return BuildModel(default(T)); 
    } 

    public object BuildModel(object obj) 
    { 
     //this is here because the compiler will complain about casting T to 
     //the first inteface parameter in the first defined BuildModel method 
     return null; 
    } 

    public IAddressModel BuildModel(IAddressModel iModel) 
    { 
     //where AddressModel inherits from IAddressModel 
     return new AddressModel(); 
    } 

    public IUserModel BuildModel(IUserModel iModel) 
    { 
     //where UserModel inherits from IUserModel 
     return new UserModel(); 
    } 
} 

이 문제는 공장처럼

라고되어

public interface IAddressModel 
{ 
} 

public interface IUserModel 
{ 
} 

은 그럼 실제 구현을 반환하는 공장 클래스가 this : new Factory().BuildModel<IAddressModel>() generics에서 런타임에 전달되는 BuildModel (...) 메서드는 항상 T의 최소 파생 형식이며,이 경우 항상 개체입니다.

그러나 new Factory().BuildModel(default(IAddressModel));을 호출하면 올바른 방법이 대체됩니다 (대부분 컴파일 타임에 수행되기 때문일 수 있습니다). 제네릭을 사용한 동적 디스패치는 호출 된 메서드가 컴파일 타임이나 런타임에 완료 되든 같아야 할지라도 가장 많이 파생 된 메서드의 메서드를 확인하지 않는 것으로 보입니다. 이상적으로 BuildModel (...) 메서드를 private으로 만들고 일반 메서드 만 노출하고 싶습니다. 동적 메서드를 사용하여 런타임에 올바른 메서드를 호출하는 다른 방법이 있습니까? 구현을 return BuildModel((dynamic)default(T))으로 변경하려고 시도했지만 디스패치 할 메소드를 결정할 수 없다는 런타임 오류가 발생합니다. contravariance와 더 많은 인터페이스로 이것을 할 수있는 방법이 있을까요?

+0

은 당신이 생각 해 봤나'새 공장 < IAddressModel>(). BuildModel()'대신에? – Bobson

+0

@Bobson - 네, 제네릭을 메서드 수준 대신 클래스 수준으로 옮겨 보았습니다. 런타임 디스패치와 아무런 차이가 없습니다. – SignalRichard

+0

이것이 실행 시간 대 컴파일 시간 문제라고 생각하지 않습니다. 필자가 아는 한,'dynamic'을 사용하지 않는다면 일반 타입과 마찬가지로 컴파일시에 제네릭이 모두 해결됩니다. 그리고'default (T)'가'null '이 될 것이기 때문에 당신은'dynamic'을 사용할 수 없기 때문에 그것이 무엇인지를 결정할 수 없습니다. 나는 이것이 공장의 표준이 '스위치'를 사용하는 이유 중 일부라고 생각한다. – Bobson

답변

2

당신은 할 수 있습니다 할 자신이 인수의 형태 T에 따라 파견 :

public class Factory 
{ 
    private Dictionary<Type, Func<object>> builders = new Dictionary<Type, Func<object>> 
    { 
     { typeof(IAddressModel), BuildAddressModel }, 
     { typeof(IUserModel), BuildUserModel } 
    }; 

    public T Build<T>() 
    { 
     Func<object> buildFunc; 
     if (builders.TryGetValue(typeof(T), out buildFunc)) 
     { 
      return (T)buildFunc(); 
     } 
     else throw new ArgumentException("No builder for type " + typeof(T).Name); 
    } 

    private static IAddressModel BuildAddressModel() 
    { 
     return new AddressModel(); 
    } 

    private static IUserModel BuildUserModel() 
    { 
     return new UserModel(); 
    } 
} 
1

코드의 현재 상태는 컴파일 할 때 명시 적 형 변환이 필요합니다.

public T BuildModel<T>() 
{ 
    return (T)BuildModel(default(T)); 
} 

BuildModel은 T를 다형 적으로 객체로 취급합니다.

public T BuildModel<T>() where T: IAddressModel 
{    
    Console.WriteLine(typeof(T)); 
    return (T)BuildModel(default(T)); 
} 

이제 컴파일러는 T가 IAddressModel임을 인식 할 수있는 충분한 정보를 가지고 : 당신이 그런 제한을 정의하지 않는 한 BuildModel은 T는 IAddressModel 또는 IUserModel 것을 알고하지 않습니다. 하지만, 이후에 나온 것은 object이 타입 안전하지 않은 파생 된 매개 변수 (공역)가되는 것입니다. 즉, C#은 유형 안전하지 않기 때문에 공변 매개 변수 유형을 지원하지 않습니다.

당신은 여전히 ​​조건부 논리를 통해 행동 같은 공장을 얻을 수 있습니다

public T BuildModel<T>() 
    { 
     T result = default(T); 

     if (typeof(T) == typeof(IAddressModel)) 
      result = (T)BuildModel((IAddressModel)result); 
     else if (typeof(T) == typeof(IUserModel)) 
      result = (T)BuildModel((IUserModel)result); 

     return result; 
    }