'Git' voodoo kaçınarak?


47

switchEle alması gereken birkaç durumu olan bir yapıya sahibim . switchAşırı faaliyet enumkombine değerler aracılığıyla yinelenen kod sorunu teşkil hangi:

// All possible combinations of One - Eight.
public enum ExampleEnum {
    One,
    Two, TwoOne,
    Three, ThreeOne, ThreeTwo, ThreeOneTwo,
    Four, FourOne, FourTwo, FourThree, FourOneTwo, FourOneThree,
          FourTwoThree, FourOneTwoThree
    // ETC.
}

Şu anda switchyapı, her bir değeri ayrı ayrı ele almaktadır:

// All possible combinations of One - Eight.
switch (enumValue) {
    case One: DrawOne; break;
    case Two: DrawTwo; break;
    case TwoOne:
        DrawOne;
        DrawTwo;
        break;
     case Three: DrawThree; break;
     ...
}

Oradaki fikri anladın. Şu anda ifbunun tek bir satırla kombinasyonları işlemek için yığılmış bir yapıya dönüştürdüm :

// All possible combinations of One - Eight.
if (One || TwoOne || ThreeOne || ThreeOneTwo)
    DrawOne;
if (Two || TwoOne || ThreeTwo || ThreeOneTwo)
    DrawTwo;
if (Three || ThreeOne || ThreeTwo || ThreeOneTwo)
    DrawThree;

Bu, okumak için kafa karıştırıcı ve bakımı zor olan inanılmaz derecede uzun mantıksal değerlendirme sorununu ortaya koymaktadır. Bunu tekrar gözden geçirdikten sonra, alternatifler hakkında düşünmeye başladım, switchdavalar arasında ters düşecek bir yapı fikrini düşündüm .

Bir gotoolayı kullanmak zorundayım çünkü C#düşmeye izin vermiyor. Bununla birlikte, switchyapı içinde atlamasına rağmen inanılmaz derecede uzun mantık zincirlerini önler ve hala kod çoğaltması getirir.

switch (enumVal) {
    case ThreeOneTwo: DrawThree; goto case TwoOne;
    case ThreeTwo: DrawThree; goto case Two;
    case ThreeOne: DrawThree; goto default;
    case TwoOne: DrawTwo; goto default;
    case Two: DrawTwo; break;
    default: DrawOne; break;
}

Bu hala yeterince temiz bir çözüm değil ve gotokaçınmak istediğim anahtar kelimeyle ilişkili bir damgalama var . Eminim bunu temizlemenin daha iyi bir yolu olmalı.


Benim sorum

Okunabilirliği ve bakımını etkilemeden bu özel durumu ele almanın daha iyi bir yolu var mı?


28
Büyük bir tartışma yapmak istiyor gibisin. Ancak goto kullanmak için daha iyi bir vaka örneğine ihtiyacınız olacak. flag enum, if ifadenizin örneği kabul edilebilir olmasa ve bana iyi görünse bile, bunu düzgünce çözer
Ewan

5
@Ewan Hiç kimse goto kullanmıyor. Linux çekirdek kaynak koduna en son ne zaman baktınız?
John Douma

6
Sen kullanmak gotoüst düzey yapısı sizin dilinizde mevcut değil iken. Bazen bir keşke olsaydı fallthru; Bu özel kullanımdan kurtulmak için anahtar kelime gotoama ah iyi.
Joshua

25
Genel olarak, gotoC # gibi yüksek seviyeli bir dilde kullanma gereksinimi duyuyorsanız , birçok başka (ve daha iyi) tasarım ve / veya uygulama alternatifini de gözden kaçırmışsınızdır. Son derece cesareti kırılmış.
code_dredd

11
C # 'da "goto case" kullanmak, daha genel bir "goto" kullanmaktan farklıdır, çünkü açık bir şekilde nasıl başaracağınızı açıklar.
Hammerite

Yanıtlar:


175

Kodları gotoifadelerle okumayı zor buluyorum . enumFarklı bir şekilde yapılandırmanızı tavsiye ederim . Örneğin, enumher bir bitin seçeneklerden birini temsil ettiği bir bit alanıysa, şöyle görünebilir:

[Flags]
public enum ExampleEnum {
    One = 0b0001,
    Two = 0b0010,
    Three = 0b0100
};

Flags özelliği, derleyiciye, örtüşmeyen değerleri ayarladığınızı söyler. Bu kodu çağıran kod uygun biti ayarlayabilir. Daha sonra neler olduğunu netleştirmek için böyle bir şey yapabilirsiniz:

if (myEnum.HasFlag(ExampleEnum.One))
{
    CallOne();
}
if (myEnum.HasFlag(ExampleEnum.Two))
{
    CallTwo();
}
if (myEnum.HasFlag(ExampleEnum.Three))
{
    CallThree();
}

Bu myEnum, bit alanlarını düzgün bir şekilde ayarlamak ve Flags özniteliği ile işaretlemek için ayarlanan kodu gerektirir . Ancak, örneğin örneğindeki enums değerlerini şu şekilde değiştirerek yapabilirsiniz:

[Flags]
public enum ExampleEnum {
    One = 0b0001,
    Two = 0b0010,
    Three = 0b0100,
    OneAndTwo = One | Two,
    OneAndThree = One | Three,
    TwoAndThree = Two | Three
};

Formda bir sayı yazdığınızda 0bxxxx, onu ikili olarak belirtirsiniz. Böylece bit 1, 2 veya 3'ü ayarladığımızı görebilirsiniz (peki, teknik olarak 0, 1 veya 2, ama siz anlıyorsunuz). Kombinasyonları sık sık birlikte ayarlayabilirseniz, kombinasyonları bit yönünde VEYA kullanarak da adlandırabilirsiniz.


5
Öyle görünüyor ! Ama C # 7.0 veya üstü olmalı, sanırım.
user1118321

31
Neyse, bir de şöyle yapabilirsiniz: public enum ExampleEnum { One = 1 << 0, Two = 1 << 1, Three = 1 << 2, OneAndTwo = One | Two, OneAndThree = One | Three, TwoAndThree = Two | Three };. C # 7 + konusunda ısrar etmenize gerek yok.
Deduplicator


13
[Flags]Nitelik derleyici bir şey işaret etmez. Bu yüzden hala enum değerlerini 2'nin gücü olarak ilan etmeniz gerekiyor.
Joe Sewell

19
@UKMonkey Ben şiddetle katılmıyorum. HasFlagyapılan işlem için açıklayıcı ve açık bir terimdir ve uygulamayı işlevsellikten soyutlar. &Diğer diller arasında yaygın olması nedeniyle kullanmak byte, bildirmek yerine bir tür kullanmaktan daha anlamlı değildir enum.
BJ Myers,

136

IMO, sorunun kökü bu kod parçasının bile olmaması gerektiğidir.

Görünüşe göre üç bağımsız koşul ve bu şartlar doğruysa yapmanız gereken üç bağımsız eyleminiz var. Öyleyse tüm bunlar, ne yapmaları gerektiğini söylemek için üç Boole bayrağı gerektiren tek bir kod parçasına dönüştürülüyor (bunları bir enum'a karıştırıp karıştırmamayacağınız) ve sonra üç bağımsız şeyin bir kombinasyonunu yapıyor? Tek sorumluluk ilkesi burada izinsiz gün geçirmiş gibi görünüyor.

Çağrıları ait olduğu üç işleve (örneğin, eylemleri yapmanız gerektiğini keşfettiğiniz yere) koyun ve bu örneklerdeki kodu geri dönüşüm kutusuna gönderin.

Üç bayrak olmayan ve üç bayrak olsaydı, 1024 farklı kombinasyonu işlemek için bu tür bir kodu genişletir miydiniz? Umarım değildir! 1024 çok fazla ise, aynı nedenle, 8 de çok fazladır.


10
Aslında, her çeşit bayrak, seçenek ve çeşitli diğer parametrelere sahip iyi tasarlanmış birçok API vardır. Bazıları aktarılabilir, bazıları doğrudan etkilere sahip olabilir ve bazıları da SRP'yi bozmadan potansiyel olarak göz ardı edilebilir. Neyse, fonksiyon bile bazı harici girişlerin kodunu çözüyor olabilir, kim bilir? Ayrıca, absürt ad absurdum sık sık absürd sonuçlara yol açar, özellikle başlangıç ​​noktası o kadar da sağlam değilse.
Deduplicator

9
Bu cevabı oyladı. Sadece GOTO’yu tartışmak istiyorsanız, sorduğunuzdan farklı bir soru bu. Sunulan kanıtlara dayanarak, toplanmaması gereken üç ayrık gereksiniminiz vardır. Bir SE açısından, alephzero'nun doğru cevap olduğunu söylüyorum.

8
@Deduplicator 1,2 / 3 kombinasyonlarının bazılarının ancak bazı kombinasyonlarının bu karmaşasına bir bakış, bunun "iyi tasarlanmış bir API" olmadığını gösteriyor.
user949300,

4
Çok fazla bu. Diğer cevaplar, sorunun ne olduğu konusunda gerçekten bir iyileşme sağlamıyor. Bu kodun yeniden yazılması gerekiyor. Daha fazla fonksiyon yazarken yanlış bir şey yok.
only_pro

3
Bu cevabı çok seviyorum, özellikle "1024 çok fazla ise, 8 de çok fazla". Fakat esasen “polimorfizm kullanın”, değil mi? Ve (zayıf) cevabım bunu söylediğim için çok saçma sapan. Halkla ilişkiler görevlinizi işe alabilir miyim? :-)
user949300

29

Hiçbir zaman gotos kullanmak biridir "çocuklara yalan" bilgisayar biliminin kavramları. Bu, zamanın% 99'unun doğru tavsiyesidir ve çok nadir görülmediği ve yeni kodlayıcılara "kullanmayın" olarak açıklanması halinde herkes için çok daha iyi olması konusunda uzmanlaşmıştır.

Yani ne zaman gerektiğini , kullanılmadan? Orada birkaç senaryo , ancak isabet gibi görünüyor temel biridir: Eğer bir devlet makinesini kodlarken . Algoritmanızın bir durum makinesinden daha iyi organize ve yapılandırılmış bir ifadesi yoksa, koddaki doğal ifadesi yapılandırılmamış dalları içerir ve bunun yapısını tartışmasız bir şekilde yapamayan bir şey yapılabilecek çok şey yoktur. devlet makinesinin kendisi daha az değil, daha belirsizdir.

Derleyici yazarları bunu bilir, bu nedenle LALR ayrıştırıcılarını * uygulayan çoğu derleyicinin kaynak kodu gotos içerir. Bununla birlikte, çok az kişi, kendi sözcük analizcilerini ve ayrıştırıcılarını kodlayacaktır.

* - IIRC, LALL gramerlerini, tekrarlayan bir şekilde inişli çıkışlı olarak, masaları veya diğer yapılandırılmamış kontrol ifadelerini atlamaya başvurmadan uygulamak mümkündür.


Şimdi sıradaki soru, "Bu örnek bu vakalardan biri mi?"

Baktığım şey şu ki, mevcut durumun işlenmesine bağlı olarak üç farklı olası sonraki durumunuz var. Bunlardan biri ("varsayılan") yalnızca bir kod satırı olduğundan, teknik olarak geçerli olduğu durumların sonunda bu kod satırına dokunarak ondan kurtulabilirsiniz. Bu sizi 2 muhtemel devlete indirger.

Kalanlardan biri ("Üç") yalnızca görebildiğim bir yerden dallanmış. Böylece ondan aynı şekilde kurtulabilirsin. Bu gibi görünen bir kod ile bitirdiniz:

switch (exampleValue) {
    case OneAndTwo: i += 3 break;
    case OneAndThree: i += 4 break;
    case Two: i += 2 break;
    case TwoAndThree: i += 5 break;
    case Three: i += 3 break;
    default: i++ break;
}

Ancak, yine de verdiğiniz oyuncak bir örnekti. "Varsayılan" değerinin önemsiz miktarda bir kod içerdiği durumlarda, "üç" birden fazla devletten geçiyorsa veya (en önemlisi) daha fazla bakım yapılması devletleri eklemek veya karmaşık hale getirmek olasıdır , dürüst olmak gerekirse daha iyi durumda olursunuz. goto'ları kullanmak (ve belki de kalması gereken iyi bir neden olmadığı sürece, işlerin devlet makinesi doğasını gizleyen enum-case yapısından kurtulmak bile).


2
@PerpetualJ - Kesinlikle daha temiz bir şey bulmana sevindim Bu gerçekten sizin durumunuzsa, goto'larla (durum ya da enum yok. Sadece etiketli bloklar ve gotolar) denemek ve okunabilirlikte nasıl karşılaştırdıklarını görmek ilginç olabilir. Olduğu söyleniyor, "goto" seçeneği sadece daha okunaklı olmak zorunda değil, aynı zamanda argümanları engellemek ve diğer geliştiricilerin jambonlu "düzeltme" girişimlerini denemek için yeterince okunabilir olmak zorunda, bu nedenle muhtemelen en iyi seçeneği buldunuz.
TED

4
"Devletlerin eklenmesi veya daha fazla bakım yapılması muhtemel" ise, "gotos kullanmaktan daha iyi" olacağınıza inanıyorum. Spagetti kodunun, yapılandırılmış koddan daha kolay korunmasının olduğunu mu iddia ediyorsunuz? Bu, ~ 40 yıllık uygulamaya karşıdır.
user949300,

5
@ user949300 - Bina devlet makinalarına karşı pratik yapmıyor, değil. Gerçek dünyadan bir örnek için bu yorumla ilgili önceki yorumlarımı okuyabilirsiniz. Eğer böyle bir kısıtlamaya sahip olmayan bir durum makinesi algoritmasına yapay bir yapı (onların doğrusal düzeni) dayatan C durumundaki düşmeleri kullanmaya çalışırsanız, her birini yeniden düzenlemek zorunda kaldığınızda kendinizi geometrik olarak artan bir karmaşıklıkla bulursunuz. yeni bir devlet için siparişler. Sadece durumları ve geçişleri kodlarsanız, algoritmanın çağırdığı gibi olmaz. Yeni devletler eklemek çok önemlidir.
TED,

8
@ user949300 - Yine, "~ 40 yıllık uygulama" yapı derleyicileri, goto'ların aslında bu sorun alanının parçaları için en iyi yol olduğunu gösteriyor, bu nedenle derleyici kaynak koduna bakarsanız, neredeyse her zaman goto'ları bulacaksınız.
TED,

3
@ user949300 Spagetti kodu sadece belirli işlevler veya kuralları kullanırken doğal olarak görünmüyor. Herhangi bir zamanda, herhangi bir dilde, işlevde veya konvansiyonda görünebilir. Sebep amaçlanan bir uygulamada söz konusu dili, işlevi veya kuralı kullanmak ve onu çıkarmak için geriye doğru bükülmesini sağlamaktır. Her zaman iş için doğru aracı kullanın ve evet, bu bazen kullanmak anlamına gelir goto.
Abion47

26

En iyi cevap, polimorfizm kullanmaktır .

IMO, if olayını daha net ve tartışmalı şekilde kısaltan bir cevap daha :

if (One || OneAndTwo || OneAndThree)
  CallOne();
if (Two || OneAndTwo || TwoAndThree)
  CallTwo();
if (Three || OneAndThree || TwoAndThree)
  CallThree();

goto Muhtemelen benim 58. seçimim ...


5
Polimorfizmi önermek için +1 olsa da, ideal olarak müşteri kodunu sadece "Çağrı () 'ya indirgeyecek şekilde basitleştirebilir, söyleme sorma kullanarak - karar mantığını alıcı müşteriden ve sınıf mekanizmasına ve hiyerarşiye sokmak için.
Erik Eidt

12
Bir şeyi ilan etmek, görünmeyen en iyi manzarayı kesinlikle çok cesurlaştırır. Ancak ek bilgi olmadan, biraz şüphecilik ve açık fikirli olmayı öneriyorum. Belki de kombinatoryal patlama yaşadı?
Deduplicator

Vay canına, polimorfizmi önerdiğim için ilk defa indirildiğimi düşünüyorum. :-)
user949300,

25
Bazı insanlar, bir problemle karşılaştığında, "Ben sadece polimorfizm kullanacağım" deyin. Şimdi iki problemleri ve polimorfizmi var.
Monica

8
Derleyicilerin durum makinalarında gotolardan kurtulmak için çalışma zamanı polimorfizmini kullanma konusunda yüksek lisans tezi yaptım. Bu oldukça yapılabilir, ancak uygulamadan beri çoğu durumda çabaya değmeyeceğini öğrendim. Hepsi elbette hatalarla sonuçlanabilecek (ve bu cevabın ihmal ettiği) bir ton OO-kurulum kodu gerektirir.
TED,

10

Neden bu değil:

public enum ExampleEnum {
    One = 0, // Why not?
    OneAndTwo,
    OneAndThree,
    Two,
    TwoAndThree,
    Three
}
int[] COUNTS = { 1, 3, 4, 2, 5, 3 }; // Whatever

int ComputeExampleValue(int i, ExampleEnum exampleValue) {
    return i + COUNTS[(int)exampleValue];
}

Tamam, katılıyorum, bu hackish (Ben bir C # geliştirici btw değilim, bu yüzden kod için özür dilerim), ama verimlilik açısından bu şart? Dizi dizini olarak enums kullanmak C # geçerlidir.


3
Evet. Kodu veri ile değiştirmenin bir başka örneği (hız, yapı ve bakım için genellikle arzu edilir).
Peter - Monica,

2
Kuşkusuz bu, sorunun en son basımı için mükemmel bir fikir. Ve ilk numaralandırmadan (yoğun bir değer aralığı) genel olarak yapılması gereken az ya da çok bağımsız eylemlerin bayraklarına gitmek için kullanılabilir.
Deduplicator

Bir C # derleyici erişimi yoktur, ama belki kod bu tür kod kullanılarak daha güvenli hale getirilebilir: int[ExempleEnum.length] COUNTS = { 1, 3, 4, 2, 5, 3 };?
Laurent Grégoire

7

Bayrakları kullanmak istemiyorsanız veya istemiyorsanız, kuyruk özyinelemeli işlevi kullanın. 64bit sürüm modunda, derleyici ifadenize çok benzeyen bir kod üretecektir goto. Sadece onunla uğraşmak zorunda değilsin.

int ComputeExampleValue(int i, ExampleEnum exampleValue) {
    switch (exampleValue) {
        case One: return i + 1;
        case OneAndTwo: return ComputeExampleValue(i + 2, ExampleEnum.One);
        case OneAndThree: return ComputeExampleValue(i + 3, ExampleEnum.One);
        case Two: return i + 2;
        case TwoAndThree: return ComputeExampleValue(i + 2, ExampleEnum.Three);
        case Three: return i + 3;
   }
}

Bu eşsiz bir çözüm! +1 Bu şekilde yapmam gerektiğini düşünmüyorum ama gelecek okuyucular için harika!
PerpetualJ

2

Kabul edilen çözüm gayet iyi ve sorununuz için somut bir çözüm. Ancak alternatif, daha soyut bir çözüm önermek istiyorum.

Tecrübelerime göre, mantık akışını tanımlamak için enums kullanımı, çoğu zaman zayıf sınıf tasarımının bir işareti olduğu için bir kod kokusudur.

Geçen yıl üzerinde çalıştığım kodda bunun gerçek dünya örneğiyle karşılaştım. Orijinal geliştirici hem ithalat hem de ihracat mantığı olan ve bir enuma göre ikisi arasında geçiş yapan tek bir sınıf yaratmıştı. Şimdi kod benzerdi ve çift kod vardı, ancak kodun okunması oldukça zor ve test edilmesi neredeyse imkansız hale gelmişti. Her ikisini de basitleştiren ve aslında rapor edilmemiş bazı hataları tespit etmeme ve ortadan kaldırmama izin veren iki ayrı sınıfa kadar yeniden düzenlemeye başladım.

Bir kez daha, mantık akışını kontrol etmek için enums kullanmanın genellikle bir tasarım problemi olduğunu belirtmeliyim. Genel durumda, Enums, olası değerlerin açıkça tanımlandığı tipte güvenli, tüketici dostu değerler sağlamak için kullanılmalıdır. Bir özellik olarak (örneğin, bir tablodaki sütun kimliği olarak) bir mantık kontrol mekanizmasından daha iyi kullanılırlar.

Soruda sunulan sorunu göz önüne alalım. Buradaki bağlamı veya bu numaralamanın neyi temsil ettiğini gerçekten bilmiyorum. Kart çekiyor mu? Resim çizme? Kan almak? Sipariş önemli mi? Ayrıca performansın ne kadar önemli olduğunu da bilmiyorum. Performans veya bellek kritikse, o zaman bu çözüm muhtemelen istediğin gibi olmayacak.

Her durumda, enum düşünelim:

// All possible combinations of One - Eight.
public enum ExampleEnum {
    One,
    Two,
    TwoOne,
    Three,
    ThreeOne,
    ThreeTwo,
    ThreeOneTwo
}

Burada sahip olduğumuz şey, farklı iş kavramlarını temsil eden birkaç farklı enum değeridir.

Bunun yerine kullanabileceğimiz şeyleri basitleştirmek için soyutlamalar.

Aşağıdaki arayüzü düşünelim:

public interface IExample
{
  void Draw();
}

Bunu daha sonra soyut bir sınıf olarak uygulayabiliriz:

public abstract class ExampleClassBase : IExample
{
  public abstract void Draw();
  // other common functionality defined here
}

Bir, iki ve üçlü Çizimleri temsil etmek için somut bir sınıfa sahip olabiliriz (argüman uğruna farklı mantıklara sahip). Bunlar potansiyel olarak yukarıda tanımlanan temel sınıfı kullanabilir, ancak DrawOne kavramının enum tarafından temsil edilen kavramdan farklı olduğunu kabul ediyorum:

public class DrawOne
{
  public void Draw()
  {
    // Drawing logic here
  }
}

public class DrawTwo
{
  public void Draw()
  {
    // Drawing two logic here
  }
}

public class DrawThree
{
  public void Draw()
  {
    // Drawing three logic here
  }
}

Ve şimdi, diğer sınıflara mantık sağlamak için oluşturulabilecek üç ayrı sınıfımız var.

public class One : ExampleClassBase
{
  private DrawOne drawOne;

  public One(DrawOne drawOne)
  {
    this.drawOne = drawOne;
  }

  public void Draw()
  {
    this.drawOne.Draw();
  }
}

public class TwoOne : ExampleClassBase
{
  private DrawOne drawOne;
  private DrawTwo drawTwo;

  public One(DrawOne drawOne, DrawTwo drawTwo)
  {
    this.drawOne = drawOne;
    this.drawTwo = drawTwo;
  }

  public void Draw()
  {
    this.drawOne.Draw();
    this.drawTwo.Draw();
  }
}

// the other six classes here

Bu yaklaşım çok daha ayrıntılı. Ancak avantajları var.

Bir hata içeren aşağıdaki sınıfı göz önünde bulundurun:

public class ThreeTwoOne : ExampleClassBase
{
  private DrawOne drawOne;
  private DrawTwo drawTwo;
  private DrawThree drawThree;

  public One(DrawOne drawOne, DrawTwo drawTwo, DrawThree drawThree)
  {
    this.drawOne = drawOne;
    this.drawTwo = drawTwo;
    this.drawThree = drawThree;
  }

  public void Draw()
  {
    this.drawOne.Draw();
    this.drawTwo.Draw();
  }
}

Eksik drawThree.Draw () çağrısını bulmak ne kadar basittir? Eğer sipariş önemliyse, beraberlik aramalarının sırasını görmek ve takip etmek de çok kolaydır.

Bu yaklaşımın dezavantajları:

  • Sunulan sekiz seçeneğin her biri ayrı bir sınıf gerektiriyor
  • Bu daha fazla bellek kullanacak
  • Bu, kodunuzu yüzeysel olarak daha büyük hale getirir
  • Bazen bu yaklaşım mümkün olmamakla birlikte, bazı değişiklikler olabilir

Bu yaklaşımın avantajları:

  • Bu sınıfların her biri tamamen test edilebilir; Çünkü
  • Beraberlik yöntemlerinin döngüsel karmaşıklığı düşüktür (ve teoride gerekirse, DrawOne, DrawTwo veya DrawThree sınıflarıyla alay edebilirim)
  • Beraberlik yöntemleri anlaşılabilir - bir geliştirici, beynini, metodun ne yaptığını hesaplayan düğümlere bağlamak zorunda değildir.
  • Hata bulmak kolay ve yazmak zor
  • Sınıflar daha yüksek seviyeli sınıflar oluşturur, bu da ThreeThreeThree sınıfını tanımlamanın kolay olduğunu gösterir

Herhangi bir karmaşık mantık kontrol kodunun vaka ifadelerine yazılması gerektiğini hissettiğinizde bu (veya benzeri) bir yaklaşımı düşünün. Gelecek, yaptığın için mutlu olacaksın.


Bu çok iyi yazılmış bir cevap ve yaklaşıma tamamen katılıyorum. Özel durumumda, bu fiillerin bir kısmı, her birinin arkasındaki önemsiz kodu göz önünde bulundurarak, sonsuz dereceye aşırı yüklenecektir. Perspektife koymak için, kodun bir Console.WriteLine (n) gerçekleştirdiğini hayal edin. Bu, çalıştığım kodun ne yaptığına eşdeğer. Diğer tüm vakaların% 90'ında, çözümünüz OOP'yi takip ederken kesinlikle en iyi cevaptır.
PerpetualJ

Evet adil nokta, bu yaklaşım her durumda yararlı değildir. Buraya koymak istedim çünkü geliştiricilerin problem çözme konusundaki belirli bir yaklaşıma sabitlenmeleri çok yaygındır ve bazen geri adım atıp alternatifler aramaktadır.
Stephen

Buna tamamen katılıyorum, aslında bu yüzden buraya gelmemin nedeni, başka bir geliştiricinin alışkanlıktan yazdığı bir şeyle ölçeklenebilirlik sorununu çözmeye çalışıyordum.
PerpetualJ

0

Burada bir anahtar kullanmaya niyetliysen, her durumu ayrı ayrı ele alırsan kodun daha hızlı olacak

switch(exampleValue)
{
    case One:
        i++;
        break;
    case Two:
        i += 2;
        break;
    case OneAndTwo:
    case Three:
        i+=3;
        break;
    case OneAndThree:
        i+=4;
        break;
    case TwoAndThree:
        i+=5;
        break;
}

her durumda sadece tek bir aritmetik işlem gerçekleştirilir

Ayrıca başkalarının da belirttiği gibi, eğer gotos kullanmayı düşünüyorsanız, muhtemelen algoritmanızı yeniden düşünmelisiniz (yine de C # 'ın vaka düşme eksikliğinin bir goto kullanmak için bir neden olabileceğini kabul ediyorum). Edgar Dijkstra'nın "Zararlı Olarak Kabul Edilen İfadesine Git" adlı ünlü makalesine bakın


3
Bunun daha basit bir sorunla karşı karşıya olan biri için harika bir fikir olduğunu söyleyebilirim. Benim durumumda o kadar basit değil.
PerpetualJ

Ayrıca, bugün Edgar'ın makalesini yazarken yaşadığı problemleri tam olarak bilmiyoruz; Günümüzde çoğu insan, bir kenara gitmekten nefret etmek için geçerli bir neden bile yok, kod okumayı zorlaştırıyor. Dürüst olmak gerekirse, kötüye kullanılması durumunda evet, aksi takdirde spagetti kodunu gerçekten yapamamanız için içerme yöntemine hapsolur. Neden bir dilin özelliği etrafında çalışmak için çaba harcıyorsunuz? Goto'nun arkaik olduğunu ve kullanım durumlarının% 99'unda diğer çözümleri düşünmeniz gerektiğini söyleyebilirim; ama eğer ihtiyacın olursa, bunun için orada.
PerpetualJ

Çok sayıda boole var olduğunda bu iyi ölçeklenemez.
user949300

0

Özel bir örneğiniz için, sayımdan gerçekten istediğiniz her şey, adımların her biri için yap / yapma göstergesi olmadığından, üç ifadenizi yeniden yazan çözüm, a'ya iftercih switchedilir ve kabul edilen cevabı vermeniz iyi olur. .

Eğer ki biraz daha karmaşık mantığı olsaydı Ama değil bu yüzden temiz çalışmak, o zaman yine bulabilir gotos switchdeyimi kafa karıştırıcı. Böyle bir şey görmeyi tercih ederim:

switch (enumVal) {
    case ThreeOneTwo: DrawThree; DrawTwo; DrawOne; break;
    case ThreeTwo:    DrawThree; DrawTwo; break;
    case ThreeOne:    DrawThree; DrawOne; break;
    case TwoOne:      DrawTwo; DrawOne; break;
    case Two:         DrawTwo; break;
    default:          DrawOne; break;
}

Bu mükemmel değil ama bence bu gotos'den daha iyi . Olayların dizileri o kadar uzunsa ve birbirlerini o kadar çoğaltırsa, her durum için tam diziyi hecelemenin bir anlamı olmazsa goto, kodun çoğaltılmasını azaltmak yerine bir alt yordamı tercih ederim .


0

Bugünlerde hiç kimsenin goto anahtar kelimesinden nefret etmek için bir nedeni olduğundan emin değilim. Kesinlikle arkaiktir ve kullanım durumlarının% 99'unda gerekli değildir, ancak bir nedenden dolayı dilin bir özelliğidir.

gotoAnahtar kelimeden nefret etmenin nedeni,

if (someCondition) {
    goto label;
}

string message = "Hello World!";

label:
Console.WriteLine(message);

Hata! Bu kesinlikle işe yaramayacak. messageDeğişken bu kod yolu tanımlı değil. Yani C # bunu geçemez. Ancak bu örtülü olabilir. Düşünmek

object.setMessage("Hello World!");

label:
object.display();

Ve displaysonra bunun içinde WriteLineifade olduğunu varsayalım .

Bu tür bir hatayı bulmak zor olabilir çünkü gotokod yolunu gizler.

Bu basitleştirilmiş bir örnek. Gerçek bir örneğin neredeyse kesin olmayacağını varsayalım. label:Ve kullanımı arasında elli kod satırı olabilir message.

Dil goto, sadece bloklardan inerek, nasıl kullanılabileceğini sınırlayarak bunu düzeltmeye yardımcı olabilir. Ancak C # goto, bununla sınırlı değildir. Kodun üstünden atlayabilir. Dahası, eğer sınırlayacaksanız goto, ismi değiştirmek de o kadar iyidir. Diğer diller break, sayıların (çıkacak blokların) veya bir etiketin (blokların birinde) bloklardan aşağıya inişlerinde kullanılır .

Kavramı gotodüşük seviyeli bir makine dili talimatıdır. Ancak, daha yüksek seviyeli dillere sahip olmamızın nedeni, değişken kapsam gibi daha yüksek seviyeli soyutlamalarla sınırlı olmamızdır.

Bütün bunlar, eğer durumdan diğerine atlamak için gotobir switchcümle içinde C # kullanıyorsanız , bu oldukça zararsızdır. Her dava zaten bir giriş noktasıdır. Daha tehlikeli formlarla gotobu zararsız kullanımını sınırladığı için, bu durumda onu aramanın aptalca olduğunu düşünüyorum goto. Bunun continueiçin böyle bir şey kullandıklarını tercih ederim. Fakat bazı nedenlerden dolayı, dili yazmadan önce bana sormayı unuttular.


2
Gelen C#dilin değişken bildirimleri üzerinden atlama olamaz. Prova için bir konsol uygulaması başlatın ve gönderinize verdiğiniz kodu girin. Etiketinizde, hatanın atanmamış yerel değişken 'message' ı kullandığını belirten bir derleyici hatası alırsınız . Buna izin verilen dillerde geçerli bir meseledir, ancak C # dilinde değildir.
PerpetualJ

0

Bu kadar çok seçeneğiniz olduğunda (ve dediğiniz gibi, daha fazlası), o zaman belki kod değil veridir.

Enum değerlerini eylemlerle eşleştiren, işlevler olarak veya eylemleri temsil eden basit bir enum türü olarak ifade edilen bir sözlük oluşturun. Ardından, kodunuz basit bir sözlük aramasına indirgenebilir, ardından işlev değeri aranır veya basitleştirilmiş seçimlerdeki bir anahtar kullanılır.


-7

Bir döngü kullanın ve kesme ve devam etme arasındaki fark.

do {
  switch (exampleValue) {
    case OneAndTwo: i += 2; break;
    case OneAndThree: i += 3; break;
    case Two: i += 2; continue;
    case TwoAndThree: i += 2;
    // dropthrough
    case Three: i += 3; continue;
    default: break;
  }
  i++;
} while(0);
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.