[C#] 이 유형보다 ‘전환 유형’에 더 좋은 대안이 있습니까?

C # switch이 유형에 있을 수없는 것으로 보았을 때 ( is관계가 하나 이상의 구별 case이 적용될 수 있기 때문에 특별한 경우로 추가되지 않았 음 ), 이것 이외의 유형에 대한 전환을 시뮬레이션하는 더 좋은 방법이 있습니까?

void Foo(object o)
{
    if (o is A)
    {
        ((A)o).Hop();
    }
    else if (o is B)
    {
        ((B)o).Skip();
    }
    else
    {
        throw new ArgumentException("Unexpected type: " + o.GetType());
    }
}



답변

C #에서는 유형 전환이 확실히 부족합니다 ( 업데이트 : C # 7 / VS 2017에서는 유형 전환이 지원됩니다- 아래 Zachary Yates의 답변 참조 ). 큰 if / else if / else 문없이이 작업을 수행하려면 다른 구조로 작업해야합니다. TypeSwitch 구조를 작성하는 방법을 자세히 설명하는 블로그 게시물을 잠시 동안 작성했습니다.

https://docs.microsoft.com/archive/blogs/jaredpar/switching-on-types

짧은 버전 : TypeSwitch는 중복 캐스팅을 방지하고 일반 switch / case 문과 유사한 구문을 제공하도록 설계되었습니다. 예를 들어 다음은 표준 Windows 양식 이벤트에서 작동중인 TypeSwitch입니다.

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"));

TypeSwitch의 코드는 실제로 매우 작으며 프로젝트에 쉽게 넣을 수 있습니다.

static class TypeSwitch {
    public class CaseInfo {
        public bool IsDefault { get; set; }
        public Type Target { get; set; }
        public Action<object> Action { get; set; }
    }

    public static void Do(object source, params CaseInfo[] cases) {
        var type = source.GetType();
        foreach (var entry in cases) {
            if (entry.IsDefault || entry.Target.IsAssignableFrom(type)) {
                entry.Action(source);
                break;
            }
        }
    }

    public static CaseInfo Case<T>(Action action) {
        return new CaseInfo() {
            Action = x => action(),
            Target = typeof(T)
        };
    }

    public static CaseInfo Case<T>(Action<T> action) {
        return new CaseInfo() {
            Action = (x) => action((T)x),
            Target = typeof(T)
        };
    }

    public static CaseInfo Default(Action action) {
        return new CaseInfo() {
            Action = x => action(),
            IsDefault = true
        };
    }
}


답변

Visual Studio 2017 (릴리스 15. *)과 함께 제공되는 C # 7 을 사용하면 case명령문 에서 패턴 을 사용할 수 있습니다 (패턴 일치).

switch(shape)
{
    case Circle c:
        WriteLine($"circle with radius {c.Radius}");
        break;
    case Rectangle s when (s.Length == s.Height):
        WriteLine($"{s.Length} x {s.Height} square");
        break;
    case Rectangle r:
        WriteLine($"{r.Length} x {r.Height} rectangle");
        break;
    default:
        WriteLine("<unknown shape>");
        break;
    case null:
        throw new ArgumentNullException(nameof(shape));
}

C # 6에서는 nameof () 연산자 와 함께 switch 문을 사용할 수 있습니다 (@Joey Adams 덕분에).

switch(o.GetType().Name) {
    case nameof(AType):
        break;
    case nameof(BType):
        break;
}

C # 5 및 이전 버전에서는 switch 문을 사용할 수 있지만 유형 이름이 포함 된 마술 문자열을 사용해야합니다 … 특별히 리팩터링하지 않습니다 (@nukefusion 덕분에)

switch(o.GetType().Name) {
  case "AType":
    break;
}


답변

하나의 옵션에서 사전 것입니다 TypeAction(또는 다른 대리자를). 유형에 따라 조치를 찾은 후 실행하십시오. 나는 지금까지 공장에서 이것을 사용했습니다.


답변

함께 JaredPar의 대답은 내 머리의 뒤쪽에, 내가 쓴 자신의 변형 TypeSwitch사용이 더 좋은 구문에 대한 추론을 입력 클래스를 :

class A { string Name { get; } }
class B : A { string LongName { get; } }
class C : A { string FullName { get; } }
class X { public string ToString(IFormatProvider provider); }
class Y { public string GetIdentifier(); }

public string GetName(object value)
{
    string name = null;
    TypeSwitch.On(value)
        .Case((C x) => name = x.FullName)
        .Case((B x) => name = x.LongName)
        .Case((A x) => name = x.Name)
        .Case((X x) => name = x.ToString(CultureInfo.CurrentCulture))
        .Case((Y x) => name = x.GetIdentifier())
        .Default((x) => name = x.ToString());
    return name;
}

Case()방법 의 순서 는 중요합니다.


TypeSwitch수업에 대한 전체 주석 코드를 가져옵니다 . 이것은 작동하는 약식 버전입니다.

public static class TypeSwitch
{
    public static Switch<TSource> On<TSource>(TSource value)
    {
        return new Switch<TSource>(value);
    }

    public sealed class Switch<TSource>
    {
        private readonly TSource value;
        private bool handled = false;

        internal Switch(TSource value)
        {
            this.value = value;
        }

        public Switch<TSource> Case<TTarget>(Action<TTarget> action)
            where TTarget : TSource
        {
            if (!this.handled && this.value is TTarget)
            {
                action((TTarget) this.value);
                this.handled = true;
            }
            return this;
        }

        public void Default(Action<TSource> action)
        {
            if (!this.handled)
                action(this.value);
        }
    }
}


답변

수퍼 클래스 (S)를 작성하고 A와 B를 상속하십시오. 그런 다음 모든 서브 클래스가 구현해야하는 추상 메소드를 S에 선언하십시오.

이렇게하면 “foo”메소드가 서명을 Foo (S o)로 변경하여 형식을 안전하게 만들 수 있으며, 그 추악한 예외를 던질 필요가 없습니다.


답변

C # 7 이상에서 패턴 일치를 사용할 수 있습니다.

switch (foo.GetType())
{
    case var type when type == typeof(Player):
        break;
    case var type when type == typeof(Address):
        break;
    case var type when type == typeof(Department):
        break;
    case var type when type == typeof(ContactType):
        break;
    default:
        break;
}


답변

명확하게 스스로를 시도하지 않고 메서드에 과부하가 걸리는 것입니다. 지금까지 대부분의 답변은 미래의 서브 클래스를 고려하지 않았기 때문에 나중에 끔찍한 유지 보수 문제가 발생할 수 있습니다.