2012-05-23 7 views
6

Jakie jest najlepsze rozwiązanie tego problemu? Próbuję utworzyć funkcję, która ma kilka opcjonalnych parametrów typów klas, dla których wartość null ma wartość znaczącą i nie może być używana jako domyślna. Jak w,C# parametr opcjonalny oprócz wartości null dla parametru klasy?

public void DoSomething(Class1 optional1, Class2 optional2, Class3 optional3) 
    { 
     if (! WasSpecified(optional1)) { optional1 = defaultForOptional1; } 
     if (! WasSpecified(optional2)) { optional2 = defaultForOptional2; } 
     if (! WasSpecified(optional3)) { optional3 = defaultForOptional3; } 

     // ... do the actual work ... 
    } 

Nie mogę korzystać Class1 optional1 = null ponieważ null jest znaczące. Nie mogę korzystać z niektórych klasy zastępczy instancji Class1 optional1 = defaultForOptional1 powodu kompilacji stałym wymogiem dla tych parametrów opcjonalnych Doszedłem się z następujących opcji:

  1. dostarczyć przeciążeń z każdej możliwej kombinacji, co oznacza 8 do przeciążeń Ta metoda.
  2. Dołącz parametr Boolean dla każdego parametru opcjonalnego, wskazujący, czy użyć wartości domyślnej, czy też podkręci podpis.

Czy ktoś wymyślił jakieś sprytne rozwiązanie?

Dzięki!

edycja: Skończyłem na pisaniu klasy opakowania, więc nie musiałem powtarzać Boolean HasFoo.

/// <summary> 
    /// A wrapper for variables indicating whether or not the variable has 
    /// been set. 
    /// </summary> 
    /// <typeparam name="T"></typeparam> 
    public struct Setable<T> 
    { 
     // According to http://msdn.microsoft.com/en-us/library/aa288208%28v=vs.71%29.aspx, 
     // "[s]tructs cannot contain explicit parameterless constructors" and "[s]truct 
     // members are automatically initialized to their default values." That's fine, 
     // since Boolean defaults to false and usually T will be nullable. 

     /// <summary> 
     /// Whether or not the variable was set. 
     /// </summary> 
     public Boolean IsSet { get; private set; } 

     /// <summary> 
     /// The variable value. 
     /// </summary> 
     public T Value { get; private set; } 

     /// <summary> 
     /// Converts from Setable to T. 
     /// </summary> 
     /// <param name="p_setable"></param> 
     /// <returns></returns> 
     public static implicit operator T(Setable<T> p_setable) 
     { 
      return p_setable.Value; 
     } 

     /// <summary> 
     /// Converts from T to Setable. 
     /// </summary> 
     /// <param name="p_tee"></param> 
     /// <returns></returns> 
     public static implicit operator Setable<T>(T p_tee) 
     { 
      return new Setable<T> 
      { 
       IsSet = true 
       , Value = p_tee 
      }; 
     } 
    } 
+1

Przeciążenia nie pozwalają przejść przez literalną stałą zerową. (Tylko kwestia stylu.) – Neil

+0

Co dokładnie próbujesz osiągnąć tutaj? –

Odpowiedz

10

bym przynajmniej rozważyć tworząc nowy typ parametru:

public void DoSomething(DoSomethingOptions options) 

... gdzie DoSomethingOptions mógłby wyglądać następująco:

public class DoSomethingOptions 
{ 
    private Class1 class1; 
    public bool HasClass1 { get; private set; } 

    public Class1 Class1 
    { 
     get { return class1; } 
     set 
     { 
      class1 = value; 
      HasClass1 = true; 
     } 
    } 

    ... for other properties ... 
} 

Następnie możesz zadzwonić pod numer:

DoSomething(new DoSomethingOptions { Class1 = null, Class2 = new Class2() }); 

Nie otrzymujesz wykładniczego zestawu przeciążeń i nadal możesz go nazwać rozsądnie zwartym.

Jest to podobne do podejścia, które przyjmuje Process z ProcessStartInfo.

+0

Dzięki za pomysł, Jon. Pójdę z tym, chyba że usłyszę o jakimś sposobie obejścia tego stałego problemu z kompilacją. –

7

zapewnić przeciążeń z każdej możliwej kombinacji, czyli 8 przeciążeń dotyczące tej metody.

Oto moje preferencje. Sprawia, że ​​sytuacja jest bardzo przejrzysta i możliwa do utrzymania. Wewnętrznie można zmapować do pojedynczej procedury inicjalizacji w celu zmniejszenia powielonego kodu.

+1

W tym scenariuszu (8 przeciążeń) można go uznać za nieco konserwowalny. Ale to oczywiście ** nie byłoby możliwe do utrzymania, ponieważ wymagane są więcej parametrów. Wyobraź sobie, że ma 5 parametrów ... to 32 wariacje! tak, jest to bardziej oczywiste na swój sposób, ale łatwość konserwacji ma ogromny wpływ. Z drugiej strony, CA1026 http://msdn.microsoft.com/en-us/library/ms182135.aspx stwierdza, że ​​nie używa parametrów opcjonalnych do publicznego dostępu ... to po prostu dużo szaleństwa. –

+0

@ m-y Realistycznie rzadko zdarza się, że KAŻDY parametr jest lub powinien być opcjonalny - to tylko te znaczące. Jeśli każdy parametr jest opcjonalny, chciałbym przekształcić się w bardziej znaczącą strukturę danych. –

+0

@ReedCopsey, to jest zgodne z odpowiedzią Jona Skeeta poniżej. Najprawdopodobniej użyję obiektu transferu, jak sugerują ciebie i Jon, ponieważ mogę mieć więcej niż trzy opcjonalne parametry. m-y ma rację, że zmiany będą nadmierne. –

1

Tak, spróbuj użyć obiektu. Zdefiniuj klasę, która obejmuje możliwe wybory. Po ustawieniu wyboru w obiekcie można go zapisać w tym samym obiekcie, jeśli został ustawiony za pomocą ustawiacza oryginalnej właściwości.

Przykład:

internal class SettingsHolder 
{ 
    public SettingsHolder() 
    { 
     IsOriginalPropADefault = true; 
    } 

    private Class1 originalProp; 
    public Class1 OriginalProp 
    { 
     get 
     { 
      return originalProp; 
     } 
     set 
     { 
      originalProp = value; 
      IsOriginalPropADefault = false; 
     } 
    } 

    public bool IsOriginalPropADefault { get; private set; } 

} 
6

Wolałbym co null znaczy "nic", a mają static readonly członek typu Class1, Class2 itp na Class1, Class2 itp nazwie None.W takim razie zamiast znaczenia null można użyć wartości NULL jako "nic", jak było pierwotnie zamierzone.

W przypadku, który jest mylące:

public class Class1 
{ 
    public static readonly Class1 None = new Class1(); 
} 
public static Class2 
{ 
    public static readonly Class2 None = new Class2(); 
} 

nocie, że jeśli null w Twoim przypadku oznacza coś innego niż „None” (podobnie jak „brakujące dane” lub coś innego) należy wymienić element wygląda następująco. Zauważ również: będzie to miało większy sens dla innych osób czytających i używających twojego kodu w przyszłości.

+0

Zobacz także Null Object pattern dla powiązanych informacji: http://en.wikipedia.org/wiki/Null_Object_pattern (and +1) – BrokenGlass

+0

+1 - Tak, zgadzam się z całym sercem. Zamiast odbierania ** prawdziwego ** znaczenia wartości zerowej, pole statyczne reprezentuje wartość "Nic". Jest to trochę jak 'String.Empty' vs null. –

+0

Tak jak powiedziałem, staram się _avoid_ używając 'null' oznaczać" Nic "lub" Nieokreślony ". W tym przypadku "null" ma znaczenie. W twoim przykładzie, w jaki sposób mogę użyć Class1.None jako parametru domyślnego bez uzyskania "Domyślnej wartości parametru dla" optional1 "musi być stałą czasu kompilacji"? 'public void DoSomething (Class1 opcjonalnie1 = Class1.None)' dostaje mi ten błąd. Dziękuję wszystkim za odpowiedzi. –

2

Można utworzyć wyliczenie Flags, które można przekazać, aby zaznaczyć używane klasy.

[Flags] 
public enum DoSomethingOptions 
{ 
    None = 0, 
    UseClass1 = 1, 
    UseClass2 = 2, 
    UseClass3 = 4, 
    etc.. 
} 

DoSomething(Class1 class1, ..., DoSomethingOptions options = DoSomethingOptions.None) { ... } 

Następnie wystarczy podać to wyliczenie, aby zaznaczyć używane klasy. Zastanawiam się, dlaczego użyłeś null do oznaczania czegoś innego niż null? Chociaż może to być rozwiązanie, chciałbym powiedzieć "przemyśleć swój projekt".

+0

+1 Sprytny pomysł. – Crisfole

+0

To świetny pomysł. Dzięki! –

Powiązane problemy