Có cách nào khác tốt hơn cái này để 'bật loại' không?


331

Xem như C # không thể switchtrên Loại (mà tôi thu thập không được thêm vào như một trường hợp đặc biệt vì các ismối quan hệ có nghĩa là casecó thể áp dụng nhiều hơn một loại khác nhau ), có cách nào tốt hơn để mô phỏng chuyển đổi trên loại khác không?

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
Vì tò mò, tại sao bạn không sử dụng đa hình?

18
@jeyoung các lớp niêm phong, và nó không đáng cho các tình huống đặc biệt
xyz



2
@jeyoung: Một tình huống điển hình trong đó đa hình không thể được sử dụng là khi các loại đang được chuyển đổi phải không biết mã có chứa switchcâu lệnh. Một ví dụ: Hội A chứa một tập hợp các đối tượng dữ liệu (sẽ không thay đổi, được xác định trong tài liệu đặc tả hoặc như vậy). Tập hợp B , CD mỗi tham chiếu A và cung cấp một chuyển đổi cho các đối tượng dữ liệu khác nhau từ A (ví dụ: tuần tự hóa / giải tuần tự hóa sang một số định dạng cụ thể). Bạn phải phản ánh toàn bộ hệ thống phân cấp lớp trong B , CD và sử dụng các nhà máy hoặc bạn có ...
HOẶC Mapper

Câu trả lời:


276

Việc bật các loại chắc chắn là thiếu C # ( CẬP NHẬT: trong C # 7 / VS 2017 chuyển đổi các loại được hỗ trợ - xem câu trả lời của Zachary Yates bên dưới ). Để thực hiện điều này mà không có câu lệnh if / other if / other lớn, bạn sẽ cần phải làm việc với một cấu trúc khác. Tôi đã viết một bài đăng trên blog một lúc sau chi tiết cách xây dựng cấu trúc TypeSwitch.

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

Phiên bản ngắn: TypeSwitch được thiết kế để ngăn việc truyền dự phòng và đưa ra một cú pháp tương tự như một câu lệnh chuyển đổi / trường hợp thông thường. Ví dụ: đây là TypeSwitch hoạt động trên một sự kiện biểu mẫu Windows tiêu chuẩn

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

Mã cho TypeSwitch thực sự khá nhỏ và có thể dễ dàng đưa vào dự án của bạn.

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" cũng có thể được thay đổi thành "entry.Target.IsAssignableFrom (type)" để đưa các loại tương thích (ví dụ: các lớp con) vào tài khoản.
Đánh dấu Cidade

Đã thay đổi mã để sử dụng "entry.Target.IsAssignableFrom (type)" để các lớp con được hỗ trợ.
Matt Howells

3
Một điều có thể đáng chú ý là (theo những gì tôi hiểu) cần phải chỉ định hành động 'mặc định' cuối cùng để đảm bảo tất cả các trường hợp khác được kiểm tra. Tôi tin rằng đây không phải là một yêu cầu trong một chuyển đổi tiêu chuẩn - không phải là tôi đã từng thấy bất kỳ ai cố gắng đặt 'mặc định' ở bất cứ nơi nào khác ngoài đáy. Một vài lựa chọn an toàn thất bại cho điều này có thể là ra lệnh cho mảng để đảm bảo mặc định là cuối cùng (lãng phí bit) hoặc bật mặc định trong một biến được xử lý sau foreach(điều này chỉ xảy ra nếu không tìm thấy kết quả khớp)
musefan

Nếu người gửi là null thì sao? GetType sẽ đưa ra một ngoại lệ
Jon

Hai đề xuất: Xử lý nguồn null bằng cách gọi mặc định hoặc ném ngoại lệ và loại bỏ boolean CaseInfobằng cách chỉ kiểm tra giá trị loại (nếu null thì đó là mặc định).
Felix K.

290

Với C # 7 , được cung cấp cùng với Visual Studio 2017 (Phiên bản 15. *), bạn có thể sử dụng Loại trong các casecâu lệnh (khớp mẫu):

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

Với C # 6, bạn có thể sử dụng câu lệnh chuyển đổi với toán tử nameof () (cảm ơn @Joey Adams):

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

Với C # 5 trở về trước, bạn có thể sử dụng câu lệnh chuyển đổi, nhưng bạn sẽ phải sử dụng chuỗi ma thuật chứa tên loại ... không thân thiện với người tái cấu trúc (cảm ơn @nukefusion)

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

1
cái này có hoạt động với case typeof (chuỗi) .Name: ... hoặc nó phải ở với Valuetype không?
Tomer W

3
Obfuscation có thể phá vỡ nó
Konrad Morawski

6
@nukefusion: Đó là, trừ khi bạn sử dụng nameof()toán tử mới sáng bóng .
Joey Adams

21
Tôi không thích câu trả lời này vì nameof (NamespaceA.ClassC) == nameof (NamespaceB.ClassC) là đúng.
ischas

7
(c # 7) bạn cũng có thể sử dụng dấu gạch dưới nếu bạn không cần quyền truy cập vào đối tượng:case UnauthorizedException _:
Assaf S.

101

Một lựa chọn là có một từ điển từ Typeđến Action(hoặc một số đại biểu khác). Tra cứu hành động dựa trên loại, và sau đó thực hiện nó. Tôi đã sử dụng nó cho các nhà máy trước đây.


31
Lưu ý nhỏ: tốt cho các trận đấu 1: 1, nhưng có thể gây khó khăn với tính kế thừa và / hoặc giao diện - đặc biệt là khi trật tự không được đảm bảo được bảo tồn bằng từ điển. Tuy nhiên, đó là cách tôi làm điều đó ở một vài nơi công bằng ;-p Vì vậy +1
Marc Gravell

@Marc: Làm thế nào để kế thừa hoặc giao diện phá vỡ trong mô hình này? Giả sử khóa là một loại và hành động là một phương thức, thì kế thừa hoặc giao diện thực sự sẽ buộc Quyền (TM), theo như tôi có thể nói. Tôi chắc chắn hiểu vấn đề với nhiều hành động và thiếu trật tự.
Harper Shelby

2
Tôi đã sử dụng kỹ thuật này rất nhiều trong quá khứ, thường là trước khi chuyển sang Container IoC
Chris Canal

4
Kỹ thuật này phá vỡ sự kế thừa và giao diện vì bạn cần một sự tương ứng một-một giữa đối tượng bạn đang kiểm tra và đại biểu bạn đang gọi. Bạn nên tìm trong nhiều giao diện của một đối tượng trong từ điển?
Robert Rossney

5
Nếu bạn đang xây dựng một từ điển cụ thể cho mục đích này, bạn có thể quá tải bộ chỉ mục để trả về giá trị của loại khóa hoặc nếu thiếu thì siêu lớp của nó, nếu thiếu thì siêu lớp đó, v.v., cho đến khi không còn gì.
Erik Forbes

49

Với câu trả lời của JaredPar ở phía sau đầu, tôi đã viết một biến thể của TypeSwitchlớp anh ta sử dụng suy luận kiểu cho một cú pháp đẹp hơn:

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

Lưu ý rằng thứ tự của các Case()phương pháp là quan trọng.


Lấy mã đầy đủ và nhận xét cho TypeSwitchlớp học của tôi . Đây là một phiên bản rút gọn làm việc:

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

Trông giống như một giải pháp tốt và muốn xem những gì bạn đã nói về nó nhưng blog đã chết.
Wes Grant

1
Chết tiệt, bạn đúng. Webhost của tôi đang gặp một số vấn đề kể từ một giờ. Họ đang làm việc trên nó. Bài đăng trên blog của tôi về cơ bản giống như câu trả lời ở đây, nhưng với một liên kết đến mã nguồn đầy đủ.
Daniel AA Pelsmaeker

1
Yêu làm thế nào điều này làm giảm một loạt các dấu ngoặc nếu chuyển sang "chức năng" đơn giản. Công việc tốt đẹp!
James White

2
Bạn cũng có thể thêm một phương thức mở rộng cho trường hợp ban đầu : public static Switch<TSource> Case<TSource, TTarget>(this TSource value, Action<TTarget> action) where TTarget : TSource. Điều này cho phép bạn nóivalue.Case((C x) ...
Joey Adams

1
@JoeyAdams: Tôi đã kết hợp đề xuất cuối cùng của bạn, cùng với một số cải tiến nhỏ. Tuy nhiên, tôi vẫn giữ nguyên cú pháp.
Daniel AA Pelsmaeker

14

Tạo một siêu lớp (S) và làm cho A và B kế thừa từ nó. Sau đó khai báo một phương thức trừu tượng trên S mà mọi lớp con cần thực hiện.

Thực hiện phương pháp "foo" này cũng có thể thay đổi chữ ký của nó thành Foo (S o), làm cho nó trở nên an toàn và bạn không cần phải ném ngoại lệ xấu xí đó.


Đúng là bruno, nhưng câu hỏi không gợi ý điều đó. Bạn có thể bao gồm điều đó trong câu trả lời của bạn mặc dù Pablo.
Dana the Sane

Từ câu hỏi tôi nghĩ A và B đủ chung chung để họ có thể là A = String; B = Danh sách <int> chẳng hạn ...
bruno conde

13

Bạn có thể sử dụng khớp mẫu trong C # 7 trở lên:

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

Cảm ơn bạn vì điều này! Cũng có thể được sử dụng để phát hiện các lớp con: if (this. typeof (RadGridView)):
Flemming Bonde Kentved

Bạn đang làm sai. Xem câu trả lời của Serge Intern và đọc về nguyên tắc thay thế Liskov
0xF

8

Bạn thực sự nên làm quá tải phương pháp của bạn, không cố gắng tự mình thực hiện định hướng. Hầu hết các câu trả lời cho đến nay không tính đến các lớp con trong tương lai, điều này có thể dẫn đến các vấn đề bảo trì thực sự khủng khiếp sau này.


3
Độ phân giải quá tải được xác định tĩnh để hoàn toàn không hoạt động.
Neutrino

@Neutrino: không có gì trong câu hỏi chỉ ra rằng loại không được biết đến vào thời gian biên dịch. Và nếu đúng như vậy, sự quá tải có ý nghĩa hơn bất kỳ tùy chọn nào khác, được đưa ra ví dụ mã gốc của OP.
Peter Duniho

Tôi nghĩ rằng việc anh ấy đang cố gắng sử dụng câu lệnh 'if' hoặc 'switch' để xác định loại là một dấu hiệu khá rõ ràng rằng loại này không được biết tại thời điểm biên dịch.
Neutrino

@Neutrino, tôi nhớ bạn rằng, như Serge Berezovskiy đã chỉ ra, có từ khóa động trong C #, đại diện cho một loại phải được giải quyết một cách linh hoạt (trong thời gian chạy, thay vì thời gian biên dịch).
Davide Cannizzo

8

Nếu bạn đang sử dụng C # 4, bạn có thể sử dụng chức năng động mới để đạt được một sự thay thế thú vị. Tôi không nói điều này tốt hơn, thực tế có vẻ như nó sẽ chậm hơn, nhưng nó có một sự tao nhã nhất định với nó.

class Thing
{

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

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

}

Và cách sử dụng:

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

Lý do điều này hoạt động là vì một lời gọi phương thức động C # 4 có quá tải được giải quyết trong thời gian chạy thay vì thời gian biên dịch. Tôi đã viết thêm một chút về ý tưởng này gần đây . Một lần nữa, tôi chỉ muốn nhắc lại rằng điều này có thể thực hiện tồi tệ hơn tất cả các đề xuất khác, tôi chỉ đưa ra nó như một sự tò mò.


1
Tôi đã có cùng một ý tưởng ngày hôm nay. Nó chậm hơn khoảng 3 lần so với chuyển đổi tên loại. Tất nhiên chậm hơn là tương đối (đối với 60.000.000 cuộc gọi, chỉ 4 giây.) Và mã này dễ đọc hơn rất nhiều, nó cũng đáng giá.
Daryl


7

Đối với các loại tích hợp, bạn có thể sử dụng phép liệt kê TypeCode. Xin lưu ý rằng GetType () là loại chậm, nhưng có lẽ không liên quan trong hầu hết các tình huống.

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

Đối với các loại tùy chỉnh, bạn có thể tạo bảng liệt kê của riêng mình và giao diện hoặc lớp cơ sở với thuộc tính hoặc phương thức trừu tượng ...

Tóm tắt lớp thực hiện tài sản

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

Lớp trừu tượng thực hiện phương pháp

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

Giao diện thực hiện tài sản

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

Giao diện thực hiện phương thức

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

Một trong những đồng nghiệp của tôi cũng chỉ nói với tôi về điều này: Điều này có lợi thế là bạn có thể sử dụng nó cho bất kỳ loại đối tượng nào, không chỉ những đối tượng mà bạn xác định. Nó có nhược điểm là lớn hơn một chút và chậm hơn.

Đầu tiên định nghĩa một lớp tĩnh như thế này:

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

Và sau đó bạn có thể sử dụng nó như thế này:

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

Cảm ơn bạn đã thêm biến thể TypeCode () - cho các kiểu nguyên thủy, bởi vì ngay cả biến thể C # 7.0 - cũng không hoạt động với các biến thể (không rõ tên () rõ ràng)
Ole Albers

6

Tôi thích cách sử dụng kiểu gõ ngầm của Virtlink để giúp chuyển đổi dễ đọc hơn nhiều, nhưng tôi không thích việc ra sớm là không thể, và chúng tôi đang thực hiện phân bổ. Hãy bật lên một chút hoàn hảo.

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

Chà, điều đó làm cho ngón tay của tôi bị tổn thương. Hãy làm điều đó trong 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#>
}

Điều chỉnh ví dụ của Virtlink một chút:

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

Có thể đọc và nhanh chóng. Bây giờ, khi mọi người tiếp tục chỉ ra câu trả lời của họ và đưa ra bản chất của câu hỏi này, thứ tự rất quan trọng trong việc khớp loại. Vì thế:

  • Đặt loại lá trước, loại cơ bản sau.
  • Đối với các loại ngang hàng, đặt nhiều khả năng phù hợp đầu tiên để tối đa hóa sự hoàn hảo.
  • Điều này ngụ ý rằng không cần phải có một trường hợp mặc định đặc biệt. Thay vào đó, chỉ cần sử dụng loại cơ bản nhất trong lambda, và đặt nó cuối cùng.

5

Được thừa kế tạo điều kiện cho một đối tượng được công nhận là nhiều hơn một loại, tôi nghĩ rằng một chuyển đổi có thể dẫn đến sự mơ hồ xấu. Ví dụ:

Trường hợp 1

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

Trường hợp 2

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

Vì s là một chuỗi một đối tượng. Tôi nghĩ rằng khi bạn viết một switch(foo)bạn mong muốn foo khớp với một và chỉ một trong những casecâu. Với một công tắc về các loại, thứ tự bạn viết các câu lệnh tình huống của bạn có thể có thể thay đổi kết quả của toàn bộ câu lệnh chuyển đổi. Tôi nghĩ rằng điều đó sẽ sai.

Bạn có thể nghĩ về trình biên dịch - kiểm tra các loại câu lệnh "typewitch", kiểm tra xem các kiểu liệt kê không kế thừa lẫn nhau. Điều đó không tồn tại mặc dù.

foo is Tkhông giống như foo.GetType() == typeof(T)!!



4

Một cách khác là định nghĩa một giao diện IThing và sau đó triển khai nó trong cả hai lớp ở đây là snipet:

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

Theo thông số kỹ thuật C # 7.0, bạn có thể khai báo một biến cục bộ nằm trong phạm vi casea 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;
}

Đây là cách tốt nhất để thực hiện một việc như vậy bởi vì nó chỉ liên quan đến các thao tác truyền và đẩy trên ngăn xếp, là các hoạt động nhanh nhất mà trình thông dịch có thể chạy chỉ sau các hoạt động và booleanđiều kiện bitwise .

So sánh điều này với một Dictionary<K, V>, đây là cách sử dụng bộ nhớ ít hơn nhiều: việc giữ một từ điển đòi hỏi nhiều không gian hơn trong RAM và một số tính toán nhiều hơn bởi CPU để tạo hai mảng (một cho các khóa và một cho các giá trị) và thu thập mã băm cho các khóa để đặt giá trị cho các khóa tương ứng của họ.

Vì vậy, ví như xa Tôi biết, tôi không tin rằng một cách nhanh hơn có thể tồn tại, trừ khi bạn muốn sử dụng chỉ là một if- then- elseblock với các isnhà khai thác như sau:

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

Bạn có thể tạo các phương thức quá tải:

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

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

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

Và truyền đối số để dynamicnhập để bỏ qua kiểm tra kiểu tĩnh:

Foo((dynamic)something);

2

Bạn đang tìm kiếm Discriminated Unionsmột tính năng ngôn ngữ của F #, nhưng bạn có thể đạt được hiệu ứng tương tự bằng cách sử dụng thư viện do tôi tạo, được gọi là OneOf

https://github.com/mcintyre321/OneOf

Ưu điểm lớn hơn switch(và ifexceptions as control flow) là nó là thời gian biên dịch an toàn - không có xử lý mặc định hoặc rơi qua

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

Nếu bạn thêm mục thứ ba vào o, bạn sẽ gặp lỗi trình biên dịch vì bạn phải thêm trình xử lý Func bên trong lệnh gọi chuyển đổi.

Bạn cũng có thể thực hiện một .Matchtrả về một giá trị, thay vì thực thi một câu lệnh:

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

2

Tạo một giao diện IFooable, sau đó tạo cho bạn ABcác lớp để thực hiện một phương thức chung, lần lượt gọi phương thức tương ứng mà bạn muốn:

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

Lưu ý, tốt hơn là sử dụng asthay vì kiểm tra trước isvà sau đó đúc, vì cách đó bạn tạo ra 2 phôi, vì vậy nó đắt hơn.


2

Tôi những trường hợp như vậy tôi thường kết thúc với một danh sách các vị từ và hành động. Một cái gì đó dọc theo những dòng này:

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

Sau khi so sánh các tùy chọn một vài câu trả lời ở đây được cung cấp cho các tính năng F #, tôi phát hiện ra F # để có cách hỗ trợ tốt hơn cho chuyển đổi dựa trên loại (mặc dù tôi vẫn gắn bó với C #).
Bạn có thể muốn xem ở đâyở đây .


2
<chèn phích cắm cho F # tại đây>
Overlord Zurg

2

Các cải tiến của C # 8 về khớp mẫu đã cho phép thực hiện như thế này. Trong một số trường hợp, nó làm công việc và ngắn gọn hơn.

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

1

Tôi sẽ tạo một giao diện với bất kỳ tên và tên phương thức nào có ý nghĩa cho chuyển đổi của bạn, hãy gọi chúng tương ứng: IDoableđiều đó nói để thực hiện 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();
    }
}

và thay đổi phương thức như sau:

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

Ít nhất với điều đó là bạn an toàn trong thời gian biên dịch và tôi nghi ngờ rằng hiệu năng khôn ngoan sẽ tốt hơn kiểm tra loại khi chạy.


0

Tôi đồng ý với Jon về việc có một hàm hành động cho tên lớp. Nếu bạn giữ mẫu của mình, bạn có thể muốn xem xét sử dụng cấu trúc "as" thay thế:

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

Sự khác biệt là khi bạn sử dụng patter if (foo là Bar) {((Bar) foo) .Action (); } bạn đang thực hiện kiểu đúc hai lần. Bây giờ có lẽ trình biên dịch sẽ tối ưu hóa và chỉ thực hiện công việc đó một lần - nhưng tôi sẽ không tin vào điều đó.


1
Tôi thực sự không thích nhiều điểm thoát (trả về), nhưng nếu bạn muốn gắn bó với điều này, hãy thêm "if (o == null) throw" vào đầu, vì sau này bạn sẽ không biết liệu diễn viên không thành công hay đối tượng là null.
Nắng Milenov

0

Như Pablo gợi ý, cách tiếp cận giao diện hầu như luôn luôn là điều đúng đắn để xử lý việc này. Để thực sự sử dụng chuyển đổi, một cách khác là có một enum tùy chỉnh biểu thị loại của bạn trong các lớp của bạn.

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:
        //......
    }
}

Đây là loại thực hiện trong BCL quá. Một ví dụ là MemberInfo.MemberTypes , một ví dụ khác GetTypeCodedành cho các kiểu nguyên thủy, như:

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

0

Đây là một câu trả lời thay thế trộn lẫn các đóng góp từ các câu trả lời của JaredPar và VirtLink, với các ràng buộc sau:

  • Cấu trúc chuyển đổi hoạt động như một chức năng và nhận các chức năng như các tham số cho các trường hợp.
  • Đảm bảo rằng nó được xây dựng chính xác và luôn tồn tại một chức năng mặc định .
  • trả về sau trận đấu đầu tiên (đúng với câu trả lời của JaredPar, không đúng với câu trả lời của VirtLink).

Sử dụng:

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

Mã số:

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ó - chỉ cần sử dụng "khớp mẫu" có tên hơi kỳ lạ từ C # 7 trở lên để khớp với lớp hoặc cấu trúc:

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

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

0

tôi sử dụng

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

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

0

Nên làm việc với

loại trường hợp _:

giống:

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

Nếu bạn biết lớp bạn đang mong đợi nhưng bạn vẫn không có đối tượng, bạn thậm chí có thể làm điều này:

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

0

Với C # 8 trở đi, bạn có thể làm cho nó ngắn gọn hơn với công tắc mới. Và với việc sử dụng tùy chọn loại bỏ _ bạn có thể tránh việc tạo các biến số trong khi bạn không cần chúng, như thế này:

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

Invoice và ShippingList là các lớp và tài liệu là một đối tượng có thể là một trong số chúng.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.