C # 제네릭 및 형식 검사


83

IList<T>매개 변수로 사용하는 방법이 있습니다. 그 T객체 의 유형이 무엇인지 확인하고 그에 따라 무언가를해야합니다. T값 을 사용하려고 했지만 컴파일러에서 허용하지 않습니다. 내 솔루션은 다음과 같습니다.

private static string BuildClause<T>(IList<T> clause)
{
    if (clause.Count > 0)
    {
        if (clause[0] is int || clause[0] is decimal)
        {
           //do something
        }
        else if (clause[0] is String)
        {
           //do something else
        }
        else if (...) //etc for all the types
        else
        {
           throw new ApplicationException("Invalid type");
        }
    } 
}

더 나은 방법이 있어야합니다. T전달 된 유형을 확인한 다음 switch문 을 사용할 수있는 방법이 있습니까?


1
저는 개인적으로 각 데이터 유형에 대해 특별히 무엇을하고 있는지 알고 싶습니다. 각 데이터 유형에 대해 대략 동일한 변환을 수행하는 경우 서로 다른 유형을 공통 인터페이스에 매핑하고 해당 인터페이스에서 작동하는 것이 더 쉬울 수 있습니다.
Juliet

답변:


121

과부하를 사용할 수 있습니다.

public static string BuildClause(List<string> l){...}

public static string BuildClause(List<int> l){...}

public static string BuildClause<T>(List<T> l){...}

또는 일반 매개 변수의 유형을 검사 할 수 있습니다.

Type listType = typeof(T);
if(listType == typeof(int)){...}

23
+1 : 과부하는 설계 및 장기 유지 보수 측면에서 확실히 최고의 솔루션입니다. 일반 매개 변수의 런타임 유형 검사는 똑바로 코딩하기에는 너무 아이러니 해 보입니다.
Juliet

이것이 유용 할 때의 좋은 예는 매우 다양한 유형의 일반 직렬화입니다. 전달되는 객체가 문자열 인 경우 추가 작업이 필요한 이유는 무엇입니까? 이 문자열의 경우, 단지 여분의 처리를 시도하지 않고 원래의 문자열을 반환
watkinsmatthewp

죄송합니다. switch-case대신 사용하여이를 달성 할 수있는 방법 이 if-else있습니까?
Tân

@HappyCoding 안타깝게도 = (다음 버전의 C #에서는 그렇게 할 수 있습니다.
jonnii

7
오버로드가 기능적으로 다른 경우 (부작용도 고려) 일반 오버로딩 ( 이 답변 참조)에 의존해서는 안됩니다 . 더 전문화 된 오버로드가 호출 될 것이라고 보장 할 수 없기 때문입니다. 규칙은 다음과 같습니다. 특정 유형에 대해 특수 논리를 수행해야하는 경우 해당 유형을 확인하고 오버로딩을 사용하지 않아야합니다. 그러나 특수한 로직 (즉, 성능 향상을 위해) 만 선호하지만 일반적인 경우를 포함한 모든 오버로드가 동일한 결과를 가져온다면 유형 검사 대신 오버로딩을 사용할 수 있습니다.
tomosius

23

사용할 수 있습니다 typeof(T).

private static string BuildClause<T>(IList<T> clause)
{
     Type itemType = typeof(T);
     if(itemType == typeof(int) || itemType == typeof(decimal))
    ...
}

7

기본적으로 좋은 방법이 없다는 것을 알고 있습니다. 얼마 전에 나는 이것에 실망했고 약간을 도왔고 구문을 조금 더 깔끔하게 만드는 약간의 유틸리티 클래스를 작성했습니다. 본질적으로 코드를

TypeSwitcher.Do(clause[0],
  TypeSwitch.Case<int>(x => ...),  // x is an int
  TypeSwitch.Case<decimal>(d => ...), // d is a decimal 
  TypeSwitch.Case<string>(s => ...)); // s is a string

전체 블로그 게시물 및 구현에 대한 세부 정보는 여기에서 확인할 수 있습니다.


6

그리고 C #이 발전했기 때문에 (이제) 패턴 일치를 사용할 수 있습니다 .

private static string BuildClause<T>(IList<T> clause)
{
    if (clause.Count > 0)
    {
        switch (clause[0])
        {
            case int x: // do something with x, which is an int here...
            case decimal x: // do something with x, which is a decimal here...
            case string x: // do something with x, which is a string here...
            ...
            default: throw new ApplicationException("Invalid type");
        }
    }
}

다시 C # 8.0의 스위치 식 을 사용하면 구문이 더욱 간결 해집니다.

private static string BuildClause<T>(IList<T> clause)
{
    if (clause.Count > 0)
    {
        return clause[0] switch
        {
            int x => "some string related to this int",
            decimal x => "some string related to this decimal",
            string x => x,
            ...,
            _ => throw new ApplicationException("Invalid type")
        }
    }
}

4

typeof 연산자 ...

typeof(T)

... c # switch 문에서는 작동하지 않습니다. 하지만 이건 어때? 다음 게시물에는 정적 클래스가 포함되어 있습니다.

'유형 켜기'에 대한 더 나은 대안이 있습니까?

... 다음과 같은 코드를 작성할 수 있습니다.

TypeSwitch.Do(
    sender,
    TypeSwitch.Case<Button>(() => textBox1.Text = "Hit a Button"),
    TypeSwitch.Case<CheckBox>(x => textBox1.Text = "Checkbox is " + x.Checked),
    TypeSwitch.Default(() => textBox1.Text = "Not sure what is hovered over"));

여기에서 JaredPar의 답변을 참조하십시오.
Robert Harvey

3

유형을 확인하고 유형을 기반으로하는 작업을하는 모든 사람에게는 제네릭에 대한 좋은 생각이 아닙니다. 저는 동의하지만 이것이 완벽하게 이해되는 상황이있을 수 있다고 생각합니다.

예를 들어 이렇게 구현 된 클래스가있는 경우 (참고 :이 코드가 단순성을 위해 수행하는 모든 작업을 표시하지 않고 여기에 잘라 붙여 넣었으므로 전체 코드가 의도 한대로 빌드하거나 작동하지 않을 수 있지만 요점을 얻습니다. 또한 Unit은 열거 형입니다) :

public class FoodCount<TValue> : BaseFoodCount
{
    public TValue Value { get; set; }

    public override string ToString()
    {
        if (Value is decimal)
        {
            // Code not cleaned up yet
            // Some code and values defined in base class

            mstrValue = Value.ToString();
            decimal mdecValue;
            decimal.TryParse(mstrValue, out mdecValue);

            mstrValue = decimal.Round(mdecValue).ToString();

            mstrValue = mstrValue + mstrUnitOfMeasurement;
            return mstrValue;
        }
        else
        {
            // Simply return a string
            string str = Value.ToString() + mstrUnitOfMeasurement;
            return str;
        }
    }
}

...

public class SaturatedFat : FoodCountWithDailyValue<decimal>
{
    public SaturatedFat()
    {
        mUnit = Unit.g;
    }

}

public class Fiber : FoodCount<int>
{
    public Fiber()
    {
        mUnit = Unit.g;
    }
}

public void DoSomething()
{
       nutritionFields.SaturatedFat oSatFat = new nutritionFields.SaturatedFat();

       string mstrValueToDisplayPreFormatted= oSatFat.ToString();
}

요약하자면, 특별한 일을하기 위해 제네릭이 어떤 유형인지 확인하려는 타당한 이유가 있다고 생각합니다.


2

원하는 작업에 switch 문을 사용할 수있는 방법이 없습니다. switch 문은 정수 유형과 함께 제공되어야합니다. 정수 유형은 "유형"개체와 같은 복합 유형이나 해당 문제에 대한 다른 개체 유형을 포함하지 않습니다.


2

당신의 구성은 일반적인 방법의 목적을 완전히 무너 뜨립니다. 그것이 무엇인지 알아 내기에 충분한 정보를 우리에게주지는 않았지만, 당신이 성취하려는 것을 성취하기위한 더 나은 방법이 있어야하기 때문에 그것은 의도적으로 추한 것입니다.


2

할 수는 typeOf(T)있지만 방법을 다시 확인하고 여기에서 단일 책임을 위반하지 않는지 확인합니다. 이것은 코드 냄새가 될 것입니다. 그렇게해서는 안된다는 의미가 아니라주의해야합니다.

제네릭의 요점은 유형이 무엇인지 상관하지 않거나 특정 기준 세트에 맞는 한 유형에 구애받지 않는 알고리즘을 구축 할 수 있다는 것입니다. 귀하의 구현은 그다지 일반적이지 않습니다.


2

도움이 되었기를 바랍니다.

  • typeof(IList<T>).IsGenericType == true
  • typeof(IList<T>).GetGenericTypeDefinition() == typeof(IList<>)
  • typeof(IList<int>).GetGenericArguments()[0] == typeof(int)

https://dotnetfiddle.net/5qUZnt


0

이건 어때 :

            // Checks to see if the value passed is valid. 
            if (!TypeDescriptor.GetConverter(typeof(T)).IsValid(value))
            {
                throw new ArgumentException();
            }
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.