2012-05-23 34 views
6

¿Cuál es la mejor solución a este problema? Estoy intentando crear una función que tenga varios parámetros opcionales de tipos de clase para los cuales null es un valor significativo y no se puede usar como valor predeterminado. Al igual que en,C# parámetro opcional además de nulo para el parámetro de clase?

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

No puedo utilizar Class1 optional1 = null porque nula es significativa. No puedo usar un poco de instancia de clase de marcador de posición Class1 optional1 = defaultForOptional1 debido a la exigencia constante de tiempo de compilación para estos parámetros opcionales se me ha ocurrido con las siguientes opciones:

  1. Proporcionar sobrecargas con todas las combinaciones posibles, lo que significa 8 sobrecargas para este método.
  2. Incluye un parámetro booleano para cada parámetro opcional que indica si se usa o no el valor predeterminado, lo que desordena la firma.

¿Alguien ha encontrado alguna solución inteligente para esto?

Gracias!

edit: Terminé escribiendo una clase contenedora para que no tuviera que seguir repitiendo 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

Las sobrecargas no le permitirán pasar la constante literal nula. (Solo un problema de estilo.) – Neil

+0

¿Qué es exactamente lo que intentas lograr aquí? –

Respuesta

10

yo al menos considerar la creación de un nuevo tipo para el parámetro:

public void DoSomething(DoSomethingOptions options) 

... donde DoSomethingOptions podría tener este aspecto:

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

Entonces puede llamarlo con:

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

No termina con un conjunto exponencial de sobrecargas, y aún así puede llamarlo razonablemente compacto.

Esto es similar al enfoque que toma Process con ProcessStartInfo.

+0

Gracias por la idea, Jon. Seguiré con esto a menos que escuche alguna forma de evitar ese problema constante en tiempo de compilación. –

7

Proporcionar sobrecargas con cada combinación posible, lo que significa 8 sobrecargas para este método.

Esta es mi preferencia. Hace que la situación sea muy clara y fácil de mantener. Internamente, puede asignar una única rutina de inicialización para reducir el código duplicado.

+1

En este escenario (8 sobrecargas) puede considerarse algo mantenible. Pero, esto claramente no ** sería ** mantenible ya que se requieren más parámetros. Imagínese si tuviera 5 parámetros ... ¡eso son 32 variaciones! sí, es más claro a su manera, pero el mantenimiento tiene un gran éxito. Por otra parte, CA1026 http://msdn.microsoft.com/en-us/library/ms182135.aspx indica no utilizar parámetros opcionales para el acceso público ... es solo una locura. –

+0

@ m-y Siendo realistas, es raro que TODOS los parámetros sean o deberían ser opcionales, por lo que solo son significativos. Si cada parámetro es opcional, me refactorizaría en una estructura de datos más significativa. –

+0

@ReedCopsey, eso es consistente con la respuesta de Jon Skeet a continuación. Lo más probable es que use un objeto de transferencia como sugieren usted y Jon, ya que podría tener más de tres parámetros opcionales. m-y tiene razón en que las variaciones serán excesivas. –

1

Sí, intenta usar un objeto. Define una clase que encapsula las posibles elecciones. Cuando se establece una opción en el objeto que puede almacenar en ese mismo objeto si se configuró mediante el uso del colocador de la propiedad original.

Un ejemplo:

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

preferiría hacer null significa "nada", y tienen un miembro static readonly de tipo Class1, Class2, etc., sobre Class1, etc. Class2 llamado None.Entonces, en lugar de hacer que null tenga sentido, puedes usar null como "nada" como se pretendía originalmente.

En caso que es confuso:

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

Tenga en cuenta, que si null en su caso significa algo distinto de "None" (como "MissingData" o algo más) debe asignar al miembro de esta manera. También tenga en cuenta: esto tendrá mucho más sentido para otras personas que lean y usen su código en el futuro.

+0

Consulte también Patrón de objeto nulo para obtener información relacionada: http://en.wikipedia.org/wiki/Null_Object_pattern (y +1) – BrokenGlass

+0

+1 - Sí, estoy totalmente de acuerdo. En lugar de eliminar el significado ** verdadero ** de nulo, tiene un campo estático que representa un valor de "Nada". Es algo así como 'String.Empty' vs null. –

+0

Como dije, estoy intentando 'evitar' usar 'null' para significar 'Nothing' o 'Unspecified'. En este caso, 'null' es significativo. En su ejemplo, ¿cómo usaría Class1.None como parámetro predeterminado sin obtener "El valor de parámetro predeterminado para 'optional1' debe ser una constante de tiempo de compilación"? 'public void DoSomething (Clase1 opcional1 = Clase1.Nono)' me da ese error. Gracias por sus respuestas a todos. –

2

Puede crear una enumeración Flags que puede pasar para marcar qué clases usar.

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

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

Luego simplemente pase esa enumeración para marcar qué clases usar. Me pregunto por qué usaste null para significar algo que no sea null? Aunque esto podría ser una solución, me gustaría decir "replantea tu diseño".

+0

+1 idea inteligente. – Crisfole

+0

Esa es una buena idea. ¡Gracias! –

Cuestiones relacionadas