2010-03-03 17 views
22

¿Hay alguna forma de pasar la propiedad de un Objeto por referencia? Sé que puedo pasar todo el objeto, pero quiero especificar una propiedad del objeto para establecer y verificar su tipo, así sé cómo analizar. ¿Debería tomar otro enfoque (no puedo cambiar el objeto original de todos modos)?C# Pasar una propiedad por referencia

public class Foo{ 
    public Foo(){} 
    public int Age { get; set; } 
} 

private void setFromQueryString(object aProperty, String queryString, HttpContext context) 
{ 
    //here I want to handle pulling the values out of 
    //the query string and parsing them or setting them 
    //to null or empty string... 
    String valueString = context.Request.QueryString[queryString].ToString(); 

    //I need to check the type of the property that I am setting. 

    //this is null so I can't check it's type 
    Type t = aProperty.GetType(); 
} 

private void callingMethod(HttpContext context) 
{ 
    Foo myFoo = new Foo(); 
    setFromQueryString(myFoo.Age, "inputAge", context); 
} 
+1

Duplicado de http://stackoverflow.com/questions/1867708/how-to-pass-properties-by-reference-in-c –

+1

Bueno, sí, el título de la pregunta es el mismo pero se pregunta en una manera muy confusa ... – ctrlShiftBryan

Respuesta

18

Usted puede llamar a la función con una expresión lambda:

private void setFromQueryString<T>(Action<T> setter, String queryString, HttpContext context) 
{ 
    //here I want to handle pulling the values out of 
    //the query string and parsing them or setting them 
    //to null or empty string... 
    String valueString = context.Request.QueryString[queryString].ToString(); 

    //I need to check the type of the property that I am setting. 

    //this is null so I can't check it's type 
    Type t = typeof(T); 
    ... 
    setter(value); 
} 

Usted lo llamaría así:

setFromQueryString<int>(i => myFoo.Age = i, "inputAge", context); 

EDITAR: Si realmente quieren inferencia de tipos :

private void setFromQueryString<T>(Func<T> getter, Action<T> setter, String queryString, HttpContext context) { 
    ... 
} 
setFromQueryString(() => myFoo.Age, i => myFoo.Age = i, "inputAge", context); 
+0

Esto se ve increíble. Implementé el método, pero la invocación indica "Los argumentos de tipo para el método" ... "No se puede inferir del uso. Intente especificar los argumentos de tipo explícitamente". – ctrlShiftBryan

+0

Esto está muy cerca, pero me obliga a hacerlo ... setFromQueryString ((int i) => myFoo.Age = i, "inputAge", context); Me gustaría inferir el tipo. – ctrlShiftBryan

+0

Ok, esto tiene sentido. Necesito que el getter pueda inferir el tipo. Gracias. – ctrlShiftBryan

5

Puede envolver la propiedad con un método correspondiente y delega y pasa a los delegados.

delegate int IntGetter<T>(T obj); 
delegate void IntSetter<T>(T obj, int value); 

int GetAge(Foo foo) 
{ 
    return foo.Age; 
} 

void SetAge(Foo foo, int value) 
{ 
    foo.Age = value; 
} 

private void callingMethod(HttpContext context) 
{ 
    Foo myFoo = new Foo(); 
    // need to also pass foo so the property can be set 
    setFromQueryString(new IntSetter<Foo>(SetAge), foo, "inputAge", context); 
} 

private void setFromQueryString<T>(
    IntSetter<T> intSetter, 
    T obj, 
    String queryString, 
    HttpContext context) 
{ 
    String valueString = context.Request.QueryString[queryString].ToString(); 
    intSetter(T, valueString); 
} 
5

No, no hay forma de pasar directamente la propiedad por referencia. Visual Basic ofrece este soporte en el lenguaje al poner el valor de la propiedad en una variable de temperatura, y luego se pasa por referencia y se reasigna a la devolución.

En C#, que sólo se puede aproximar este comportamiento pasando un Func<T> para obtener el valor de la propiedad, y un Action<T> para ajustar el valor (utilizando cierres), donde T es el tipo de la propiedad.

2

¿Por qué no utilizar genéricos y devolver el objeto?

private T setFromQueryString<T>(String queryString, HttpContext context) 
{ 
    String valueString = context.Request.QueryString[queryString].ToString(); 

    // Shouldn't be null any more 
    Type t = typeof(T); 
} 

private void callingMethod(HttpContext context) 
{ 
    Foo myFoo = new Foo(); 
    myFoo.Age = setFromQueryString<int>("inputAge", context); 
} 
No

muy seguro de por qué estás tan establece en la inferencia, pero dado que usted es, usted podría hacer esta función

private void setFromQueryString(ref T aProperty, String queryString, HttpContext context) 
{ 
    String valueString = context.Request.QueryString[queryString].ToString(); 

    // Shouldn't be null any more 
    Type t = typeof(T); 
} 

private void callingMethod(HttpContext context) 
{ 
    Foo myFoo = new Foo(); 
    setFromQueryString(ref myFoo.Age, "inputAge", context); 
} 
+0

Este es un enfoque, pero no deseo especificar el tipo "" en la llamada. Eso se debe deducir de lo que se transmite. – ctrlShiftBryan

+0

Bastante seguro de que se deducirá en C# 3 – pdr

+0

No, el mismo problema que con la solución SLaks. No inferirá el tipo. – ctrlShiftBryan

2

Pasando con lambda es probablemente el más elegante, pero si lo que desea una solución simple a su problema

private void callingMethod(HttpContext context) 
{ 
    Foo myFoo = new Foo(); 
    int myAge = myFoo.Age; 
    setFromQueryString(ref myAge, "inputAge", context); 
    myFoo.Age = myAge;  
} 

private void setFromQueryString(ref int age, String queryString, HttpContext context) 
{ 
... 
} 
1

¿Por qué lo hace tan complicado? Conoce el tipo de la propiedad en tiempo de compilación, sólo lo hacen de forma sencilla con una línea de código:

Foo.Age = int.Parse(context.Request.QueryString["Parameter"]); 

Si es necesario comprobar el tipo, basta con añadir un poco de función que envuelve int.TryParse() y devuelve un resultado inocuo (por ejemplo, 0) si obtiene "pdq" en el valor de la cadena de consulta en lugar de un número.

+0

Por supuesto, esta es la forma normal de hacerlo, pero estoy tratando de hacer algo más reutilizable. Quiero ver si hay una manera de eliminar la redundancia del int.Parse así como encapsular alguna otra lógica de manejo de errores. – ctrlShiftBryan

+1

Lo entiendo, pero está sacrificando bastante legibilidad y capacidad de mantenimiento para nada en realidad; no hay reutilización de código porque es una línea de código, y no está haciendo que sea más elegante o más fácil de codificar, IMO. Una solución realmente elegante, una que uso, es crear clases (derivadas de System.Web.UI.Page) para sus páginas web que contienen los parámetros de cadena de consulta como propiedades y establecerlos en una anulación OnLoad, de esta manera están disponibles como this.PropertyName en el código detrás de la página, y haz tu verificación de tipo en esa clase discreta. Voy a publicar otra respuesta ... –

5

Como han señalado otros, puede hacerlo utilizando delegados, usando una de las muchas maneras de especificar delegados. Sin embargo, si tiene la intención de hacer esto con regularidad, debería considerar crear un tipo de contenedor para pasar propiedades por referencia que envuelva a los delegados necesarios, puede crear una API más agradable.

Por ejemplo:

class PropertyReference<T> 
{ 
    public T Value 
    { 
     get 
     { 
      return this.getter(); 
     } 

     set 
     { 
      this.setter(value); 
     } 
    } 

    public PropertyReference(Func<T> getter, Action<T> setter) 
    { 
     this.getter = getter; 
     this.setter = setter; 
    } 
} 

De este modo puede pasar alrededor de una referencia a su propiedad y modificarlo estableciendo el valor de referencia.

var reference = new PropertyReference(
         () => this.MyValue, 
         x => this.MyValue = x); 

reference.Value = someNewValue; 
1

Aquí es una solución totalmente diferente para usted:

crear clases derivadas de System.Web.UI.Page que tienen los parámetros de cadena de consulta como propiedades. Además, al usar una función de utilidad (vea ConvertType, a continuación), no necesita hacer demasiado para obtener los datos de QueryString. Por último, dentro de esas clases derivadas, defina una clase interna estática que contenga constantes que son los nombres de los parámetros de QueryString para que no necesite hacer referencia a ningún valor mágico en ningún lugar.

generalmente defino una clase de página de base para mi proyecto, lo que hace que sea un lugar conveniente para hacer las cosas comunes que ocurren en todas las páginas, así como un par de funciones de utilidad:

public class MyBasePage : System.Web.UI.Page 
{ 

    public T GetQueryStringValue<T>(
     string value, 
     T defaultValue, 
     bool throwOnBadConvert) 
    { 
    T returnValue; 

    if (string.IsNullOrEmpty(value)) 
     return defaultValue; 
    else 
     returnValue = ConvertType<T>(value, defaultValue); 

    if (returnValue == defaultValue && throwOnBadConvert) 
     // In production code, you'd want to create a custom Exception for this 
     throw new Exception(string.Format("The value specified '{0}' could not be converted to type '{1}.'", value, typeof(T).Name)); 
    else 
     return returnValue; 
    } 

    // I usually have this function as a static member of a global utility class because 
    // it's just too useful to only have here. 
    public T ConvertType<T>(
     object value, 
     T defaultValue) 
    { 
    Type realType = typeof(T); 

    if (value == null) 
     return defaultValue; 

    if (typeof(T) == value.GetType()) 
     return (T)value; 

    if (typeof(T).IsGenericType) 
     realType = typeof(T).GetGenericArguments()[0]; 

    if (realType == typeof(Guid)) 
     return (T)Convert.ChangeType(new Guid((string)value), realType); 
    else if (realType == typeof(bool)) 
    { 
     int i; 
     if (int.TryParse(value.ToString(), out i)) 
     return (T)Convert.ChangeType(i == 0 ? true : false, typeof(T)); 
    } 

    if (value is Guid && typeof(T) == typeof(string)) 
     return (T)Convert.ChangeType(((Guid)value).ToString(), typeof(T)); 

    if (realType.BaseType == typeof(Enum)) 
     return (T)Enum.Parse(realType, value.ToString(), true); 

    try 
    { 
     return (T)Convert.ChangeType(value, realType); 
    } 
    catch 
    { 
     return defaultValue; 
    } 
    } 
} 

public class MyPage : MyBasePage 
{ 
    public static class QueryStringParameters 
    { 
    public const string Age= "age"; 
    } 

    public int Age 
    { 
    get 
    { 
    return base.GetQueryStringValue<int>(Request[QueryStringParameters.Age], -1); 
    } 
    } 
} 

Luego, en su página normal, en el código subyacente, que ahora se ve así:

public partial class MyWebPage : MyPage 
{ 
    protected void Page_Load(object sender, EventArgs e) 
    { 
    Foo myFoo = new Foo(); 
    Foo.Age = this.Age; 
    } 
} 

esto hace que el código detrás de clases muy (como se puede ver), y es fácil de mantener, porque se hace todo el trabajo pesado limpia por dos funciones (Obtener QueryStringValue y ChangeType) que se reutilizan en cada una de las clases de página, y todo es seguro para tipos (notará en GetQueryStringValue que puede especificar si la función se lanza si el valor no se puede convertir o simplemente usa el valor predeterminado de devolución; ambos son apropiados en diferentes momentos, dependiendo de su aplicación).

Además, puede escribir fácilmente un plugin VS o un script CodeSmith para generar la clase de página derivada con bastante facilidad. Y no hay un montón de delegados y cosas que se pasen, que encuentro que a los nuevos desarrolladores les cuesta mucho entender.

Cuestiones relacionadas