이 유형보다 '전환 유형'에 더 좋은 대안이 있습니까?


331

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());
    }
}

18
호기심에서 다형성을 사용하지 않는 이유는 무엇입니까?

18
@jeyoung은 수업을 봉인했으며 임시 상황에는 그만한 가치가 없습니다
xyz



2
@jeyoung : 다형성을 사용할 수없는 일반적인 상황 중 하나는 전환되는 유형이 switch명령문 이 포함 된 코드를 알아야하는 경우 입니다. 예를 들면 다음과 같습니다. 어셈블리 A 에는 일련의 데이터 개체 (사양 문서 등에서 정의되거나 변경되지 않음)가 포함되어 있습니다. 어셈블리 B , C , 및 D 각각의 기준 로부터 각종 데이터 객체에 대한 전환 제공 (어떤 특정한 포맷으로 예를 들면, 직렬화 / 역 직렬화). B , C , D 의 전체 클래스 계층 구조를 미러링하고 팩토리를 사용해야합니다.
또는 매퍼

답변:


276

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
        };
    }
}

26
"type == entry.Target"을 "entry.Target.IsAssignableFrom (type)"으로 변경하여 호환 가능한 유형 (예 : 서브 클래스)을 고려할 수도 있습니다.
Mark Cidade

하위 클래스가 지원되도록 "entry.Target.IsAssignableFrom (type)"을 사용하도록 코드를 변경했습니다.
Matt Howells

3
주목할만한 것은 아마도 다른 모든 경우를 확인하기 위해 '기본'작업을 마지막으로 지정해야한다는 것입니다. 나는 이것이 표준 스위치의 요구 사항이 아니라고 생각합니다. 아무도 바닥 이외의 다른 곳에서 '기본'을 심는 것을 본 적이 없습니다. 이것에 대한 몇 가지 실패 방지 옵션은 기본값을 마지막으로 설정 (비트 낭비)하도록 배열을 주문하거나 이후에 처리 할 변수에 기본값을 표시하는 foreach것입니다 (일치하지 않는 경우에만 발생)
musefan

발신자가 null이면 어떻게 되나요? GetType에서 예외가 발생 함
Jon

두 가지 제안 : 기본값을 호출하거나 예외를 throw하여 null 소스를 처리 CaseInfo하고 유형 값 (null 인 경우 기본값)을 확인 하여 부울을 제거하십시오 .
Felix K.

291

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;
}

1
이 경우 caseofof (string) .Name : ... 또는 Valuetype과 함께 사용해야합니까?
Tomer W

3
난독 화는 그것을 깨뜨릴 수 있습니다
Konrad Morawski

6
@nukefusion : 반짝이는 새 nameof()연산자 를 사용하지 않는 한 .
Joey Adams

21
nameof (NamespaceA.ClassC) == nameof (NamespaceB.ClassC)가 true이므로이 답변이 마음에 들지 않습니다.
ischas

7
(c # 7) 객체에 액세스 할 필요가없는 경우 밑줄을 사용할 수도 있습니다.case UnauthorizedException _:
Assaf S.

101

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


31
사소한 참고 : 1 : 1 일치에는 좋지만 상속 및 / 또는 인터페이스에 통증이있을 수 있습니다. 특히 순서가 사전과 함께 보존되지는 않습니다. 그러나 여전히, 그것은 내가 공정한 몇몇 장소에서 그것을하는 방법이다 ;-p So +1
Marc Gravell

@Marc :이 패러다임에서 어떻게 상속이나 인터페이스가 깨질까요? 키가 유형이고 동작이 메소드라고 가정하면 상속 또는 인터페이스는 실제로 내가 알 수있는 한 Right Thing (TM)을 강제해야합니다. 나는 여러 가지 행동과 순서가 부족한 문제를 확실히 이해합니다.
하퍼 쉘비

2
나는 과거에 IoC 컨테이너로 옮기기 전에이 기술을 많이 사용했다
Chris Canal

4
이 기술은 확인중인 개체와 호출중인 대리자간에 일대일 대응이 필요하므로 상속 및 인터페이스에 대해 분류됩니다. 사전에서 객체의 여러 인터페이스 중 어떤 것을 찾아야합니까?
Robert Rossney

5
이 목적을 위해 특별히 사전을 작성하는 경우 인덱서를 오버로드하여 키 유형의 값을 반환하거나 누락 된 경우 슈퍼 클래스가 누락되면 슈퍼 클래스가 누락 될 때까지 슈퍼 클래스 등을 반환 할 수 있습니다.
Erik Forbes

49

함께 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);
        }
    }
}

좋은 해결책처럼 보이고 그것에 대해 다른 말을하고 싶지만 블로그는 죽었습니다.
웨스 그랜트

1
대니, 네 말이 맞아 내 웹 호스트에 한 시간 이후 문제가 있습니다. 그들은 노력하고 있습니다. 내 블로그의 게시물은 본질적으로 여기의 답변과 동일하지만 전체 소스 코드에 대한 링크가 있습니다.
Daniel AA Pelsmaeker 2018 년

1
이것이 간단한 "기능적"스위치에 대한 if 대괄호를 어떻게 줄일 수 있는지를 좋아합니다. 잘 하셨어요!
제임스 화이트

2
초기 사례에 대한 확장 방법을 추가 할 수도 있습니다 public static Switch<TSource> Case<TSource, TTarget>(this TSource value, Action<TTarget> action) where TTarget : TSource. 이렇게 말할 수 있습니다value.Case((C x) ...
Joey Adams

1
@JoeyAdams : 마지막 제안과 함께 약간의 개선이 이루어졌습니다. 그러나 구문을 동일하게 유지합니다.
Daniel AA Pelsmaeker

14

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

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


진실한 브루노이지만 그 질문은 그것을 암시하지 않습니다. 파블로를 통해 답에 포함시킬 수 있습니다.
Dana the Sane

질문에서 나는 A와 B가 A = String 일 수있을 정도로 일반적이라고 생각합니다. B = 예 : <int> ...
bruno conde

13

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;
}

이것에 감사합니다! 서브 클래스도 감지하는 데 사용할 수 있습니다. if (this.TemplatedParent.GetType (). IsSubclassOf (typeof (RadGridView)))를 다음으로 변경할 수 있습니다. subRadGridView.IsSubclassOf ( typeof (RadGridView)) :
Flemming Bonde Kentved

당신은 잘못하고 있습니다. Serge Intern답변을 보고 Liskov 대체 원리
0xF

8

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


3
과부하 해결은 정적으로 결정되므로 전혀 작동하지 않습니다.
Neutrino 2016 년

@ Neutrino : 컴파일 타임에 유형을 알 수 없다는 질문에 아무것도 없습니다. 그리고 그렇다면 OP의 원래 코드 예제를 고려할 때 과부하가 다른 옵션보다 더 의미가 있습니다.
피터 Duniho

유형을 결정하기 위해 'if'또는 'switch'문을 사용하려고한다는 사실은 유형이 컴파일 타임에 알려지지 않았 음을 나타내는 명확한 증거입니다.
Neutrino

@Neutrino, Sergey Berezovskiy가 지적했듯이 C #에는 동적으로 해결 해야하는 유형 (컴파일 타임이 아닌 런타임시)을 나타내는 동적 키워드가 있음을 기억합니다.
Davide Cannizzo '

8

C # 4를 사용하는 경우 새로운 동적 기능을 사용하여 흥미로운 대안을 얻을 수 있습니다. 나는 이것이 더 낫다고 말하지는 않지만 실제로 그것이 느려질 것 같지만 그것에는 어떤 우아함이 있습니다.

class Thing
{

  void Foo(A a)
  {
     a.Hop();
  }

  void Foo(B b)
  {
     b.Skip();
  }

}

그리고 사용법 :

object aOrB = Get_AOrB();
Thing t = GetThing();
((dynamic)t).Foo(aorB);

이것이 작동하는 이유는 C # 4 동적 메소드 호출에서 컴파일 시간이 아닌 런타임시 과부하가 해결 되었기 때문입니다. 나는 최근 에이 아이디어에 대해 조금 더 썼다 . 다시 말하지만, 이것이 다른 모든 제안보다 성능이 떨어질 수 있다는 점을 반복하고 싶습니다. 호기심으로 간단히 제안하고 있습니다.


1
나는 오늘 같은 생각을 가지고 있었다. 유형 이름을 전환하는 것보다 약 3 배 느립니다. 물론 속도가 상대적으로 느리고 (60,000,000 호출, 단 4 초) 코드가 훨씬 더 읽기 쉽습니다.
Daryl


7

기본 제공 형식의 경우 TypeCode 열거 형을 사용할 수 있습니다. GetType ()은 느리지 만 대부분의 상황에서는 관련이 없습니다.

switch (Type.GetTypeCode(someObject.GetType()))
{
    case TypeCode.Boolean:
        break;
    case TypeCode.Byte:
        break;
    case TypeCode.Char:
        break;
}

커스텀 타입의 경우, 당신은 당신의 자신의 열거와 추상 속성이나 메소드를 가진 인터페이스 또는 기본 클래스를 만들 수 있습니다 ...

재산의 추상 클래스 구현

public enum FooTypes { FooFighter, AbbreviatedFool, Fubar, Fugu };
public abstract class Foo
{
    public abstract FooTypes FooType { get; }
}
public class FooFighter : Foo
{
    public override FooTypes FooType { get { return FooTypes.FooFighter; } }
}

메소드의 추상 클래스 구현

public enum FooTypes { FooFighter, AbbreviatedFool, Fubar, Fugu };
public abstract class Foo
{
    public abstract FooTypes GetFooType();
}
public class FooFighter : Foo
{
    public override FooTypes GetFooType() { return FooTypes.FooFighter; }
}

속성의 인터페이스 구현

public enum FooTypes { FooFighter, AbbreviatedFool, Fubar, Fugu };
public interface IFooType
{
    FooTypes FooType { get; }
}
public class FooFighter : IFooType
{
    public FooTypes FooType { get { return FooTypes.FooFighter; } }
}

메소드의 인터페이스 구현

public enum FooTypes { FooFighter, AbbreviatedFool, Fubar, Fugu };
public interface IFooType
{
    FooTypes GetFooType();
}
public class FooFighter : IFooType
{
    public FooTypes GetFooType() { return FooTypes.FooFighter; }
}

내 동료 중 한 명이 이것에 대해서도 말해주었습니다. 이것은 당신이 정의한 것만이 아니라 문자 그대로 모든 유형의 객체에 사용할 수 있다는 이점이 있습니다. 조금 더 크고 느리다는 단점이 있습니다.

먼저 다음과 같이 정적 클래스를 정의하십시오.

public static class TypeEnumerator
{
    public class TypeEnumeratorException : Exception
    {
        public Type unknownType { get; private set; }
        public TypeEnumeratorException(Type unknownType) : base()
        {
            this.unknownType = unknownType;
        }
    }
    public enum TypeEnumeratorTypes { _int, _string, _Foo, _TcpClient, };
    private static Dictionary<Type, TypeEnumeratorTypes> typeDict;
    static TypeEnumerator()
    {
        typeDict = new Dictionary<Type, TypeEnumeratorTypes>();
        typeDict[typeof(int)] = TypeEnumeratorTypes._int;
        typeDict[typeof(string)] = TypeEnumeratorTypes._string;
        typeDict[typeof(Foo)] = TypeEnumeratorTypes._Foo;
        typeDict[typeof(System.Net.Sockets.TcpClient)] = TypeEnumeratorTypes._TcpClient;
    }
    /// <summary>
    /// Throws NullReferenceException and TypeEnumeratorException</summary>
    /// <exception cref="System.NullReferenceException">NullReferenceException</exception>
    /// <exception cref="MyProject.TypeEnumerator.TypeEnumeratorException">TypeEnumeratorException</exception>
    public static TypeEnumeratorTypes EnumerateType(object theObject)
    {
        try
        {
            return typeDict[theObject.GetType()];
        }
        catch (KeyNotFoundException)
        {
            throw new TypeEnumeratorException(theObject.GetType());
        }
    }
}

그런 다음 다음과 같이 사용할 수 있습니다.

switch (TypeEnumerator.EnumerateType(someObject))
{
    case TypeEnumerator.TypeEnumeratorTypes._int:
        break;
    case TypeEnumerator.TypeEnumeratorTypes._string:
        break;
}

하는 TypeCode ()를 추가 주셔서 감사합니다 - 기본 유형의 변형 때문에 심지어는 C # 7.0 - 변형 것과없는 일을하지 않습니다 (어느 쪽도 분명히) nameof (수행)
올레 알버스

6

Virtlink의 암시 적 타이핑 을 사용하여 스위치를 훨씬 더 읽기 쉽게 만들었습니다. 성능을 조금 높여 봅시다.

public static class TypeSwitch
{
    public static void On<TV, T1>(TV value, Action<T1> action1)
        where T1 : TV
    {
        if (value is T1) action1((T1)value);
    }

    public static void On<TV, T1, T2>(TV value, Action<T1> action1, Action<T2> action2)
        where T1 : TV where T2 : TV
    {
        if (value is T1) action1((T1)value);
        else if (value is T2) action2((T2)value);
    }

    public static void On<TV, T1, T2, T3>(TV value, Action<T1> action1, Action<T2> action2, Action<T3> action3)
        where T1 : TV where T2 : TV where T3 : TV
    {
        if (value is T1) action1((T1)value);
        else if (value is T2) action2((T2)value);
        else if (value is T3) action3((T3)value);
    }

    // ... etc.
}

글쎄, 그게 내 손가락이 아프다. T4에서 해봅시다 :

<#@ template debug="false" hostSpecific="true" language="C#" #>
<#@ output extension=".cs" #>
<#@ Assembly Name="System.Core.dll" #>
<#@ import namespace="System.Linq" #> 
<#@ import namespace="System.IO" #> 
<#
    string GenWarning = "// THIS FILE IS GENERATED FROM " + Path.GetFileName(Host.TemplateFile) + " - ANY HAND EDITS WILL BE LOST!";
    const int MaxCases = 15;
#>
<#=GenWarning#>

using System;

public static class TypeSwitch
{
<# for(int icase = 1; icase <= MaxCases; ++icase) {
    var types = string.Join(", ", Enumerable.Range(1, icase).Select(i => "T" + i));
    var actions = string.Join(", ", Enumerable.Range(1, icase).Select(i => string.Format("Action<T{0}> action{0}", i)));
    var wheres = string.Join(" ", Enumerable.Range(1, icase).Select(i => string.Format("where T{0} : TV", i)));
#>
    <#=GenWarning#>

    public static void On<TV, <#=types#>>(TV value, <#=actions#>)
        <#=wheres#>
    {
        if (value is T1) action1((T1)value);
<# for(int i = 2; i <= icase; ++i) { #>
        else if (value is T<#=i#>) action<#=i#>((T<#=i#>)value);
<#}#>
    }

<#}#>
    <#=GenWarning#>
}

Virtlink의 예제를 약간 조정 :

TypeSwitch.On(operand,
    (C x) => name = x.FullName,
    (B x) => name = x.LongName,
    (A x) => name = x.Name,
    (X x) => name = x.ToString(CultureInfo.CurrentCulture),
    (Y x) => name = x.GetIdentifier(),
    (object x) => name = x.ToString());

읽기 쉽고 빠릅니다. 이제 모두가 답을 계속 지적 하고이 질문의 성격을 고려할 때 순서는 유형 일치에서 중요합니다. 따라서:

  • 잎 유형을 먼저, 기본 유형을 나중에 넣으십시오.
  • 피어 유형의 경우 성능을 최대화하기 위해 일치 가능성을 먼저 두십시오.
  • 이는 특별한 기본 사례가 필요하지 않음을 의미합니다. 대신 람다에서 가장 먼 유형을 사용하고 마지막에 넣으십시오.

5

상속을 통해 객체를 여러 유형으로 인식 할 수 있다면 스위치가 모호성을 유발할 수 있다고 생각합니다. 예를 들면 다음과 같습니다.

사례 1

{
  string s = "a";
  if (s is string) Print("Foo");
  else if (s is object) Print("Bar");
}

사례 2

{
  string s = "a";
  if (s is object) Print("Foo");
  else if (s is string) Print("Bar");
}

s는 문자열 객체 이기 때문에 . 나는 당신이 글을 쓸 때 switch(foo)foo가 case문장 중 하나만 일치 한다고 생각 합니다. 스위치 켜기 유형을 사용하면 사례 문을 작성하는 순서에 따라 전체 스위치 문의 결과가 변경 될 수 있습니다. 나는 그것이 틀릴 것이라고 생각한다.

열거 된 형식이 서로 상속되지 않는지 확인하면서 "typeswitch"문의 형식에 대한 컴파일러 검사를 생각할 수 있습니다. 그래도 존재하지 않습니다.

foo is T와 동일하지 않습니다 foo.GetType() == typeof(T)!



4

또 다른 방법은 인터페이스 IThing을 정의한 다음 두 클래스 모두에서 구현하는 것입니다. 여기에는 스 니펫이 있습니다.

public interface IThing
{
    void Move();
}

public class ThingA : IThing
{
    public void Move()
    {
        Hop();
    }

    public void Hop(){  
        //Implementation of Hop 
    }

}

public class ThingA : IThing
{
    public void Move()
    {
        Skip();
    }

    public void Skip(){ 
        //Implementation of Skip    
    }

}

public class Foo
{
    static void Main(String[] args)
    {

    }

    private void Foo(IThing a)
    {
        a.Move();
    }
}

4

C # 7.0 사양에 따라 다음 case중 하나로 범위가 지정된 지역 변수를 선언 할 수 있습니다 switch.

object a = "Hello world";
switch (a)
{
    case string myString:
        // The variable 'a' is a string!
        break;
    case int myInt:
        // The variable 'a' is an int!
        break;
    case Foo myFoo:
        // The variable 'a' is of type Foo!
        break;
}

이는 비트 단위 작업 및 boolean조건 직후에 인터프리터가 실행할 수있는 가장 빠른 작업 인 캐스팅 및 스택 스택 작업 만 포함하기 때문에 이러한 작업을 수행하는 가장 좋은 방법 입니다.

이것과를 비교하면 Dictionary<K, V>메모리 사용량이 훨씬 적습니다. 사전을 유지하려면 RAM에 더 많은 공간이 필요하고 CPU에 의해 두 개의 배열 (키 하나와 값 하나)을 만들고 키를 넣을 해시 코드를 수집하기 위해 더 많은 계산이 필요합니다 각각의 키에 대한 값.

지금까지 나는 알고에 대한 그래서, 난 당신이 단지를 사용하지 않으려면 빠른 방법이 존재할 수 있다고 생각하지 않습니다 if- then- else블록을 함께 is조작은 다음과 같습니다 :

object a = "Hello world";
if (a is string)
{
    // The variable 'a' is a string!
} else if (a is int)
{
    // The variable 'a' is an int!
} // etc.

3

오버로드 된 메소드를 작성할 수 있습니다.

void Foo(A a) 
{ 
    a.Hop(); 
}

void Foo(B b) 
{ 
    b.Skip(); 
}

void Foo(object o) 
{ 
    throw new ArgumentException("Unexpected type: " + o.GetType()); 
}

dynamic정적 유형 검사를 무시하려면 인수를 유형으로 캐스팅하십시오 .

Foo((dynamic)something);

3

패턴 일치의 C # 8 향상으로 이와 같이 수행 할 수있었습니다. 어떤 경우에는 일을 더 간결하게합니다.

        public Animal Animal { get; set; }
        ...
        var animalName = Animal switch
        {
            Cat cat => "Tom",
            Mouse mouse => "Jerry",
            _ => "unknown"
        };

2

Discriminated UnionsF #의 언어 기능을 찾고 있지만 OneOf라는 내가 만든 라이브러리를 사용하여 비슷한 효과를 얻을 수 있습니다.

https://github.com/mcintyre321/OneOf

switch(및 ifand exceptions as control flow)에 비해 가장 큰 장점 은 컴파일 타임에 안전하다는 것입니다. 기본 처리기가 없거나 실패합니다.

void Foo(OneOf<A, B> o)
{
    o.Switch(
        a => a.Hop(),
        b => b.Skip()
    );
}

o에 세 번째 항목을 추가하면 스위치 호출 내에 핸들러 Func를 추가해야하므로 컴파일러 오류가 발생합니다.

.Match명령문을 실행하지 않고 값을 반환하는를 수행 할 수도 있습니다 .

double Area(OneOf<Square, Circle> o)
{
    return o.Match(
        square => square.Length * square.Length,
        circle => Math.PI * circle.Radius * circle.Radius
    );
}

2

인터페이스를 작성 IFooable하여 확인 후, A그리고 B다시 원하는 해당 메소드를 호출하는 일반적인 방법을 구현하는 클래스를 :

interface IFooable
{
    public void Foo();
}

class A : IFooable
{
    //other methods ...

    public void Foo()
    {
        this.Hop();
    }
}

class B : IFooable
{
    //other methods ...

    public void Foo()
    {
        this.Skip();
    }
}

class ProcessingClass
{
    public void Foo(object o)
    {
        if (o == null)
            throw new NullRefferenceException("Null reference", "o");

        IFooable f = o as IFooable;
        if (f != null)
        {
            f.Foo();
        }
        else
        {
            throw new ArgumentException("Unexpected type: " + o.GetType());
        }
    }
}

참고, 그것은 더 나은 것을 사용하는 as대신 처음으로 확인 is이 더 비싼, 그래서 그런 식으로 당신이, 2 캐스트를 만들기, 주조 후합니다.


2

나는 그런 경우에 보통 술어와 행동의 목록으로 끝납니다. 이 라인을 따라 뭔가 :

class Mine {
    static List<Func<object, bool>> predicates;
    static List<Action<object>> actions;

    static Mine() {
        AddAction<A>(o => o.Hop());
        AddAction<B>(o => o.Skip());
    }

    static void AddAction<T>(Action<T> action) {
        predicates.Add(o => o is T);
        actions.Add(o => action((T)o);
    }

    static void RunAction(object o) {
        for (int i=0; o < predicates.Count; i++) {
            if (predicates[i](o)) {
                actions[i](o);
                break;
            }
        }
    }

    void Foo(object o) {
        RunAction(o);
    }
}

2

여기에 F # 기능에 대한 몇 가지 답변을 비교 한 후 F #에서 유형 기반 전환을 더 잘 지원할 수 있다는 것을 알았습니다 (여전히 C #을 고수하고 있음). 여기여기
를보고 싶을 수도 있습니다 .


2
<여기 F 번호 삽입 플러그>
군주 저그

1

: 나는 어떤 이름 및 스위치에 대한 감각을 만들 것입니다 방법 이름의 인터페이스를 만들 것의 각각을 부르 자 IDoable그 구현 알려줍니다 void Do().

public interface IDoable
{
    void Do();
}

public class A : IDoable
{
    public void Hop() 
    {
        // ...
    }

    public void Do()
    {
        Hop();
    }
}

public class B : IDoable
{
    public void Skip() 
    {
        // ...
    }

    public void Do()
    {
        Skip();
    }
}

다음과 같이 방법을 변경하십시오.

void Foo<T>(T obj)
    where T : IDoable
{
    // ...
    obj.Do();
    // ...
}

적어도 컴파일 타임에 안전하고 성능면에서 런타임에 유형을 확인하는 것이 낫다고 생각합니다.


1

C # 8부터는 새로운 스위치로 더 간결하게 만들 수 있습니다. 그리고 버리기 옵션 _을 사용하면 불필요한 변수가 필요 없을 때 다음과 같이 생성하지 않아도됩니다.

        return document switch {
            Invoice _ => "Is Invoice",
            ShippingList _ => "Is Shipping List",
            _ => "Unknown"
        };

송장 및 배송 목록은 클래스이며 문서는 둘 중 하나 일 수있는 객체입니다.


0

나는 클래스 이름에 대한 행동 해시를 갖는 것에 대해 Jon에게 동의합니다. 패턴을 유지하는 경우 "as"구문을 대신 사용하는 것이 좋습니다.

A a = o as A;
if (a != null) {
    a.Hop();
    return;
}
B b = o as B;
if (b != null) {
    b.Skip();
    return;
}
throw new ArgumentException("...");

차이점은 패턴을 사용할 때 if (foo is Bar) {((Bar) foo) .Action (); } 타입 캐스팅을 두 번하고 있습니다. 아마도 컴파일러는 최적화하고 한 번만 작동하지만, 나는 그것을 의지하지 않을 것입니다.


1
나는 여러 개의 이탈 포인트 (반환)를 정말로 좋아하지 않지만, 이것을 고수하고 싶다면 처음에 "if (o == null) throw"를 추가하십시오. 나중에 캐스트가 실패했는지 알 수 없으므로 개체가 null입니다.
Sunny Milenov

0

Pablo가 제안했듯이 인터페이스 접근 방식은 거의 항상 이것을 처리하는 것이 옳은 일입니다. 스위치를 실제로 사용하려면 다른 대안은 클래스에서 유형을 나타내는 사용자 정의 열거 형을 갖는 것입니다.

enum ObjectType { A, B, Default }

interface IIdentifiable
{
    ObjectType Type { get; };
}
class A : IIdentifiable
{
    public ObjectType Type { get { return ObjectType.A; } }
}

class B : IIdentifiable
{
    public ObjectType Type { get { return ObjectType.B; } }
}

void Foo(IIdentifiable o)
{
    switch (o.Type)
    {
        case ObjectType.A:
        case ObjectType.B:
        //......
    }
}

이것은 BCL에서도 구현됩니다. 하나의 예는 MemberInfo.MemberTypes 이고 다른 하나는 GetTypeCode다음과 같은 기본 유형에 대한 것입니다.

void Foo(object o)
{
    switch (Type.GetTypeCode(o.GetType())) // for IConvertible, just o.GetTypeCode()
    {
        case TypeCode.Int16:
        case TypeCode.Int32:
        //etc ......
    }
}

0

이것은 JaredPar 및 VirtLink 답변의 기여를 다음과 같은 제약 조건과 혼합하는 대체 답변입니다.

  • 스위치 구성 은 기능으로 작동하며 기능 을 케이스에 대한 매개 변수 로 수신 합니다 .
  • 올바르게 빌드되고 항상 기본 기능 이 있는지 확인하십시오 .
  • 그것은 첫번째 경기 후 반환 (VirtLink 일에 대한 사실 JaredPar 응답하지 마찬가지).

용법:

 var result = 
   TSwitch<string>
     .On(val)
     .Case((string x) => "is a string")
     .Case((long x) => "is a long")
     .Default(_ => "what is it?");

암호:

public class TSwitch<TResult>
{
    class CaseInfo<T>
    {
        public Type Target { get; set; }
        public Func<object, T> Func { get; set; }
    }

    private object _source;
    private List<CaseInfo<TResult>> _cases;

    public static TSwitch<TResult> On(object source)
    {
        return new TSwitch<TResult> { 
            _source = source,
            _cases = new List<CaseInfo<TResult>>()
        };
    }

    public TResult Default(Func<object, TResult> defaultFunc)
    {
        var srcType = _source.GetType();
       foreach (var entry in _cases)
            if (entry.Target.IsAssignableFrom(srcType))
                return entry.Func(_source);

        return defaultFunc(_source);
    }

    public TSwitch<TResult> Case<TSource>(Func<TSource, TResult> func)
    {
        _cases.Add(new CaseInfo<TResult>
        {
            Func = x => func((TSource)x),
            Target = typeof(TSource)
        });
        return this;
    }
}

0

예-C # 7에서 약간 이상한 이름의 "패턴 일치"를 사용하여 클래스 또는 구조와 일치시킵니다.

IObject concrete1 = new ObjectImplementation1();
IObject concrete2 = new ObjectImplementation2();

switch (concrete1)
{
    case ObjectImplementation1 c1: return "type 1";         
    case ObjectImplementation2 c2: return "type 2";         
}

0

나는 사용한다

    public T Store<T>()
    {
        Type t = typeof(T);

        if (t == typeof(CategoryDataStore))
            return (T)DependencyService.Get<IDataStore<ItemCategory>>();
        else
            return default(T);
    }

0

함께 일해야한다

케이스 유형 _ :

처럼:

int i = 1;
bool b = true;
double d = 1.1;
object o = i; // whatever you want

switch (o)
{
    case int _:
        Answer.Content = "You got the int";
        break;
    case double _:
        Answer.Content = "You got the double";
        break;
    case bool _:
        Answer.Content = "You got the bool";
        break;
}

0

기대하는 클래스를 알고 있지만 여전히 개체가 없으면이 작업을 수행 할 수도 있습니다.

private string GetAcceptButtonText<T>() where T : BaseClass, new()
{
    switch (new T())
    {
        case BaseClassReview _: return "Review";
        case BaseClassValidate _: return "Validate";
        case BaseClassAcknowledge _: return "Acknowledge";
        default: return "Accept";
    }
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.