क्या इससे बेहतर विकल्प 'स्विच ऑन टाइप' है?


331

सी # के रूप में देखना 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कथन है। एक उदाहरण: असेंबली में डेटा ऑब्जेक्ट्स का एक सेट होता है (जो बदलने नहीं जा रहा है, विनिर्देश दस्तावेज़ या ऐसे में परिभाषित किया गया है)। सभाओं बी , सी , और डी प्रत्येक संदर्भ एक और से विभिन्न डेटा वस्तुओं के लिए एक रूपांतरण प्रदान करते हैं एक (जैसे एक क्रमबद्धता / अक्रमांकन कुछ विशेष प्रारूप के लिए)। आपको या तो बी , सी , और डी में पूरी कक्षा के पदानुक्रम को दर्पण करना होगा , और कारखानों का उपयोग करना होगा, या आपके पास ...
या मैपर

जवाबों:


276

प्रकारों पर स्विच करना निश्चित रूप से C # की कमी है ( UPDATE: C # 7 / VS 2017 में प्रकारों पर स्विच करने का समर्थन किया गया है - नीचे Zachary Yates का उत्तर देखें )। ऐसा करने के लिए बिना किसी बड़े / यदि / अन्यथा कथन के, तो आपको एक अलग संरचना के साथ काम करना होगा। मैंने एक ब्लॉग पोस्ट लिखा है कि कुछ समय बाद टाइपस्विच संरचना कैसे बनाई जाए।

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

लघु संस्करण: टाइपस्विच को निरर्थक कास्टिंग को रोकने और एक सिंटैक्स देने के लिए डिज़ाइन किया गया है जो सामान्य स्विच / केस स्टेटमेंट के समान है। उदाहरण के लिए, यहां एक मानक विंडोज फॉर्म ईवेंट पर टाइपस्विच है

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

टाइपस्विच के लिए कोड वास्तव में बहुत छोटा है और आसानी से आपकी परियोजना में डाला जा सकता है।

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 (प्रकार)" में भी बदला जा सकता है।
मार्क सिडेड

"Entry.Target.IsAssignableFrom (प्रकार)" का उपयोग करने के लिए कोड को बदल दिया ताकि उपवर्गों का समर्थन हो।
मैट हॉवेल्स

3
एक बात शायद ध्यान देने योग्य है कि (जो मुझे समझ में आता है) अन्य सभी मामलों की जाँच के लिए अंतिम 'डिफ़ॉल्ट' कार्रवाई को निर्दिष्ट करना आवश्यक है। मेरा मानना ​​है कि यह मानक स्विच में एक आवश्यकता नहीं है - ऐसा नहीं है कि मैंने कभी भी किसी को भी नीचे की तुलना में कहीं भी 'डिफ़ॉल्ट' लगाने की कोशिश करते देखा है। इस के लिए सुरक्षित विकल्प विफल होने का एक जोड़ा यह सुनिश्चित करने के लिए सरणी को ऑर्डर करने के लिए हो सकता है कि यह अंतिम है (थोड़ा बेकार) या किसी चर में डिफ़ॉल्ट को पॉप करने के बाद संसाधित किया जा सकता है foreach(जो कि केवल तब होगा जब कोई मैच नहीं मिला था)
म्यूज़फ़ैन

क्या होगा अगर प्रेषक अशक्त है? गेटटेप एक अपवाद फेंक देगा
जॉन

दो सुझाव: डिफ़ॉल्ट कॉल करके या एक अपवाद को फेंककर अशक्त स्रोत को संभालें और CaseInfoप्रकार के मूल्य के खिलाफ जाँच करके बूलियन से छुटकारा पाएं (यदि इसका शून्य डिफ़ॉल्ट है)।
फेलिक्स के।

291

C # 7 के साथ , जिसे विज़ुअल स्टूडियो 2017 (रिलीज़ 15. *) के साथ भेज दिया गया है, आप 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 () ऑपरेटर (धन्यवाद @Joey एडम्स) के साथ एक स्विच स्टेटमेंट का उपयोग कर सकते हैं :

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

C # 5 और पहले के साथ, आप एक स्विच स्टेटमेंट का उपयोग कर सकते हैं, लेकिन आपको टाइप नाम वाले मैजिक स्ट्रिंग का उपयोग करना होगा ... जो विशेष रूप से रिफलेक्टर फ्रेंडली नहीं है (धन्यवाद @nukefusion)

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

1
क्या यह केस टाइपोफ़ (स्ट्रिंग) के साथ काम करता है। नाम: ... या इसका वैल्यूएटाइप के साथ होना है?
तोमर डब्ल्यू

3
विचलन इसे तोड़ सकता है
कोनराड मोरावस्की

6
@ न्यूफ़्यूज़न: जब तक आप चमकदार नए nameof()ऑपरेटर का उपयोग नहीं करते हैं ।
जॉय एडम्स

21
मुझे यह उत्तर पसंद नहीं है क्योंकि nameof (NamespaceA.ClassC) == nameof (NamespaceB.ClassC) सत्य है।
ischas

7
(c # 7) यदि आप वस्तु तक पहुंच की आवश्यकता नहीं है, तो आप अंडरस्कोर का उपयोग भी कर सकते हैं:case UnauthorizedException _:
Assaf S.

101

एक विकल्प के Typeलिए Action(या कुछ अन्य प्रतिनिधि) से एक शब्दकोश है । प्रकार के आधार पर कार्रवाई देखें, और फिर इसे निष्पादित करें। मैंने अब से पहले कारखानों के लिए इसका उपयोग किया है।


31
लघु नोट: 1: 1 मैचों के लिए अच्छा है, लेकिन विरासत और / या इंटरफेस के साथ एक दर्द हो सकता है - विशेष रूप से आदेश के रूप में एक शब्दकोश के साथ संरक्षित करने की गारंटी नहीं है। लेकिन फिर भी, यह जिस तरह से मैं एक निष्पक्ष कुछ स्थानों में यह कर ;-p है तो +1
मार्क Gravell

@ मारक: इस प्रतिमान में वंशानुक्रम या इंटरफेस कैसे टूटेंगे? कुंजी मान लेना एक प्रकार है, और कार्रवाई एक विधि है, तो विरासत या इंटरफेस वास्तव में राइट थिंग (टीएम) को बाध्य करना चाहिए, जहां तक ​​मैं बता सकता हूं। मैं निश्चित रूप से कई कार्यों और आदेश की कमी के साथ मुद्दे को समझता हूं।
हार्पर शेल्बी

2
मैंने अतीत में इस तकनीक का उपयोग किया है, आमतौर पर एक IoC कंटेनर में जाने से पहले
क्रिस नहर

4
यह तकनीक वंशानुक्रम और इंटरफेस के लिए टूट जाती है क्योंकि आपको जिस वस्तु की जाँच हो रही है और जिस प्रतिनिधि को आप बुला रहे हैं, उसके बीच एक-से-एक पत्राचार की आवश्यकता है। आपको किसी ऑब्जेक्ट के कई इंटरफेस में से किस शब्दकोश में खोजने की कोशिश करनी चाहिए?
रॉबर्ट रॉसनी

5
यदि आप इस उद्देश्य के लिए विशेष रूप से एक शब्दकोश का निर्माण कर रहे हैं, तो आप प्रमुख प्रकार के मूल्य को वापस करने के लिए अनुक्रमणिका को अधिभारित कर सकते हैं, या यदि गायब है तो उसका सुपरक्लास, यदि वह गायब है तो वह सुपरक्लास इत्यादि, जब तक कि कुछ भी नहीं बचा है।
एरिक फोर्ब्स

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
डारन, तुम सही हो। मेरी वेबहोस्ट एक घंटे से कुछ मुद्दों पर चल रही है। वे इस पर काम कर रहे हैं। मेरे ब्लॉग पर पोस्ट मूल रूप से उत्तर के समान है, लेकिन पूर्ण स्रोत कोड के लिंक के साथ।
डैनियल एए पल्सेमेकर

1
प्यार करें कि यह कैसे कोष्ठक के एक समूह को सरल "कार्यात्मक" स्विच में बदल देता है। अच्छा काम!
जेम्स व्हाइट

2
आप प्रारंभिक मामले के लिए एक एक्सटेंशन विधि भी जोड़ सकते हैं public static Switch<TSource> Case<TSource, TTarget>(this TSource value, Action<TTarget> action) where TTarget : TSource:। इससे आप कहते हैंvalue.Case((C x) ...
जॉय एडम्स

1
@JoeyAdams: मैंने आपके अंतिम सुझाव को कुछ छोटे सुधारों के साथ शामिल किया है। हालाँकि, मैं सिंटैक्स वही रख रहा हूँ।
डैनियल एए Pelsmaeker

14

एक सुपरक्लास (एस) बनाएं और उससे ए और बी वारिस बनाएं। फिर एस पर एक सार विधि घोषित करें जिसे हर उपवर्ग को लागू करने की आवश्यकता है।

ऐसा करने से "फू" विधि अपने हस्ताक्षर को फू (एस ओ) में भी बदल सकती है, जिससे यह सुरक्षित हो जाता है, और आपको उस बदसूरत अपवाद को फेंकने की आवश्यकता नहीं है।


सच ब्रूनो, लेकिन सवाल यह सुझाव नहीं देता है। आप अपने उत्तर में हालांकि पाब्लो को शामिल कर सकते हैं।
द साने

सवाल से मुझे लगता है कि ए और बी पर्याप्त सामान्य हैं कि वे ए = स्ट्रिंग हो सकते हैं; उदाहरण के लिए 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;
}

इसके लिए धन्यवाद! उपवर्गों का पता लगाने के लिए भी उपयोग किया जा सकता है: यदि (यह .emplatedParent.GetType ()। IsSubclassOf (typeof (RadGridView))) को परिवर्तित किया जा सकता है: सबरेडग्रिड व्यू होने पर स्विच (यह .emplatedParent.GetType) () केस var subRadGridView। टाइपोफ़ (रेडग्रिड्यू)):
फ्लेमिंग बोंड ने

तुम उसे गलत कर रहे। सर्ज इंटर्न द्वारा उत्तर देखें और
लिस्कोव

8

आपको वास्तव में अपनी पद्धति को ओवरलोड करना चाहिए, न कि स्वयं को अक्षम करने की कोशिश करना। अब तक के अधिकांश उत्तर भविष्य के उपवर्गों को ध्यान में नहीं रखते हैं, जो आगे चलकर वास्तव में भयानक रखरखाव मुद्दों को जन्म दे सकता है।


3
अधिभार संकल्प को सांख्यिकीय रूप से निर्धारित किया जाता है ताकि बस काम न करें।
न्यूट्रिनो

@ न्यूट्रिनो: इस सवाल में कुछ भी नहीं है कि यह निर्धारित करता है कि संकलन समय पर टाइप नहीं किया गया है। और अगर यह है, तो ओपी के मूल कोड उदाहरण को देखते हुए, एक अधिभार किसी भी अन्य विकल्प की तुलना में अधिक समझ में आता है।
पीटर डुनिहो

मुझे लगता है कि वह इस प्रकार का निर्धारण करने के लिए 'if' या 'switch' स्टेटमेंट का उपयोग करने की कोशिश कर रहा है, यह एक स्पष्ट संकेत है कि इस प्रकार का संकलन समय पर ज्ञात नहीं है।
न्यूट्रिनो

@ न्यूट्रीनो, मुझे आपको याद है कि, जैसा कि सर्गेई बेरेज़ोवस्की ने बताया था, सी # में डायनामिक कीवर्ड है, जो एक प्रकार का प्रतिनिधित्व करता है जिसे गतिशील रूप से हल किया जाना है (संकलन के समय के बजाय रनटाइम पर)।
डेविड कैन्निज़ो

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 सेकंड।), और कोड इतना अधिक पठनीय है कि यह इसके लायक है।
डेरिल

8

हां, C # 7 के लिए धन्यवाद जो हासिल किया जा सकता है। यहां बताया गया है कि यह कैसे किया जाता है ( अभिव्यक्ति पैटर्न का उपयोग करके ):

switch (o)
{
    case A a:
        a.Hop();
        break;
    case B b:
        b.Skip();
        break;
    case C _: 
        return new ArgumentException("Type C will be supported in the next version");
    default:
        return new ArgumentException("Unexpected type: " + o.GetType());
}


7

अंतर्निहित प्रकारों के लिए, आप टाइपकोड एन्यूमरेशन का उपयोग कर सकते हैं। कृपया ध्यान दें कि गेटटाइप () एक तरह से धीमा है, लेकिन संभवतः अधिकांश स्थितियों में प्रासंगिक नहीं है।

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

टाइपकोड जोड़ने के लिए धन्यवाद () - आदिम प्रकारों के लिए संस्करण, क्योंकि यहां तक ​​कि सी # 7.0 - संस्करण उन लोगों के साथ काम नहीं करता है (न ही नामोफ़ () स्पष्ट रूप से)
ओले अल्बर्स

6

स्विच को और अधिक पठनीय बनाने के लिए मुझे अंतर्निहित टाइपिंग का उपयोग पसंद आया , लेकिन मुझे यह पसंद नहीं था कि एक शुरुआती आउट संभव नहीं है, और हम आवंटन कर रहे हैं। चलो पूर्ण को थोड़ा मोड़ दें।

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

एक छोटे से उदाहरण को समायोजित करना:

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

यह देखते हुए कि विरासत एक वस्तु को एक से अधिक प्रकार के रूप में पहचाने जाने की सुविधा देता है, मुझे लगता है कि एक स्विच खराब अस्पष्टता का कारण बन सकता है। उदाहरण के लिए:

मामला एक

{
  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)तो आप उम्मीद करते हैं कि कोई एक और केवल एक caseबयान से मेल खाता है । प्रकारों पर एक स्विच के साथ, जिस क्रम में आप अपने केस स्टेटमेंट लिखते हैं वह संभवतः पूरे स्विच स्टेटमेंट के परिणाम को बदल सकता है। मुझे लगता है कि यह गलत होगा।

आप एक "टाइपस्विच" स्टेटमेंट के प्रकारों पर एक कंपाइलर-जांच के बारे में सोच सकते हैं, यह जांच कर सकते हैं कि एन्यूमरेटेड प्रकार एक दूसरे से विरासत में नहीं मिलते हैं। हालांकि यह मौजूद नहीं है।

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

सी के लिए # 7.0 विनिर्देश के रूप में, आप एक स्थानीय चर एक में scoped घोषणा कर सकते हैं 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> , यहाँ बहुत कम स्मृति उपयोग है: एक शब्दकोश रखने के लिए रैम में अधिक स्थान की आवश्यकता होती है और 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 Unions जो F # की एक भाषा सुविधा है, लेकिन आप मेरे द्वारा बनाए गए पुस्तकालय, जिसे OneOf कहा जाता है, का उपयोग करके एक समान प्रभाव प्राप्त कर सकते हैं

https://github.com/mcintyre321/OneOf

से अधिक प्रमुख लाभ switch(और ifऔर exceptions as control flowवहाँ कोई डिफ़ॉल्ट हैंडलर है या के माध्यम से गिर -) है कि यह संकलन समय सुरक्षित है

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

यदि आप तीसरे आइटम को ओ में जोड़ते हैं, तो आपको एक कंपाइलर त्रुटि मिलेगी क्योंकि आपको स्विच कॉल के अंदर एक हैंडलर फंक जोड़ना होगा।

आप एक ऐसा भी कर सकते हैं .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

मैं जॉन के साथ वर्ग नाम के कार्यों की हैश होने के बारे में सहमत हूं। यदि आप अपना पैटर्न रखते हैं, तो आप इसके बजाय "जैसा" निर्माण का उपयोग करने पर विचार कर सकते हैं:

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

अंतर यह है कि जब आप पैटर का उपयोग करते हैं यदि (फू बार है) {(बार) फू) ।शन (); } आप दो बार टाइप कास्टिंग कर रहे हैं। अब शायद कंपाइलर ऑप्टिमाइज़ करेगा और केवल उस काम को एक बार करेगा - लेकिन मैं इस पर भरोसा नहीं करूंगा।


1
मुझे वास्तव में एकाधिक निकास बिंदु (रिटर्न) पसंद नहीं हैं, लेकिन यदि आप इसके साथ रहना चाहते हैं, तो शुरुआत में "अगर (ओ == नल) फेंक" जोड़ें, क्योंकि बाद में आपको पता नहीं चलेगा कि क्या कास्ट असफल है, या वस्तु अशक्त थी।
सनी मिलीनोव

0

जैसा कि पाब्लो सुझाव देते हैं, इसको संभालने के लिए इंटरफ़ेस अप्रोच हमेशा सही काम है। वास्तव में स्विच का उपयोग करने के लिए, एक अन्य विकल्प एक कस्टम एनम है जो आपकी कक्षाओं में आपके प्रकार को दर्शाता है।

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

यह बीसीएल में भी लागू है। एक उदाहरण मेंबरइन्फो.मेम्बरटाइप्स है , एक और 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 उत्तरों से योगदान को मिलाता है:

  • स्विच निर्माण एक फ़ंक्शन के रूप में व्यवहार करता है , और मामलों के मापदंडों के रूप में फ़ंक्शन प्राप्त करता है ।
  • यह सुनिश्चित करता है कि यह ठीक से बनाया गया है, और हमेशा एक डिफ़ॉल्ट फ़ंक्शन मौजूद है ।
  • यह पहले मैच के बाद लौटता है (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

हां - कक्षा या संरचना पर मेल करने के लिए सी # 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";
    }
}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.