C# Pass a property by reference

隐身守侯 提交于 2019-12-18 03:06:47

问题


Is there anyway to pass the property of an Object by reference? I know I can pass the whole object but I want to specify a property of the object to set and check it's type so I know how to parse. Should I maybe take another approach (I cannot change the original object in anyway)?

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:


You can call the function with a lambda expression:

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

You would call it like this:

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

EDIT: If you really want type inference:

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



回答2:


As others have pointed out, you can do this using delegates, using one of the many ways to specify delegates. However, if you intend to do this regularly, you should consider creating a wrapper type for passing properties by reference that wraps the delegates required, it may create a nicer API.

For example:

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

That way you can pass around a reference to your property and modify it by setting the reference value.

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

reference.Value = someNewValue;



回答3:


No, there is no way to directly pass the property by reference. Visual Basic offers this support in the language by putting the value of the property into a temp variable, and then that is passed by reference and reassigned upon return.

In C#, you can only approximate this behavior by passing a Func<T> to get the property value, and an Action<T> for setting the value (using closures), where T is the type of the property.




回答4:


You could wrap the property with a corresponding methods and delegates and pass the delegates.

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:


Passing function with lambda is probably most elegant, but if you just want a simple solution to your problem

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



回答6:


Why not use generics and return the object?

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

Not quite sure why you're so set on inference, but given that you are, you could do this

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



回答7:


Why are you making it so complicated? You know the type of the property at compile time, just do it the easy way with one line of code:

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

If you need to check the type, just add a little function that wraps int.TryParse() and returns an innocuous result (e.g. 0) if you get "pdq" in the querystring value instead of a number.




回答8:


Here's a totally different solution for you:

Create classes derived from System.Web.UI.Page that have the QueryString parameters as properties. Also, using a utility function (see ConvertType, below), you don't need to do too much to get the data out of the QueryString. Lastly, inside those derived classes, define a static inner class that holds constants that are the names of the QueryString parameters so that you don't need to reference any magic values anywhere.

I usually define a base page class for my project, which makes it a convenient place to do common things that happen on all pages, as well as a few utility functions:

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

Then, in your regular page, in the code behind, it now looks like this:

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

It makes the code behind classes very clean (as you can see), and it's easily maintainable because all the heavy-lifting is done by two functions (GetQueryStringValue and ChangeType) that are reused in each of the page classes, and everything is type-safe (you'll notice in GetQueryStringValue that you can specify whether the function throws if the value can't be converted or just uses returns default value; both are appropriate at different times, depending on your app).

Furthermore, you can even easily write a VS plugin or CodeSmith script to generate the derived page class quite easily. And there aren't a bunch of delegates and stuff being passed around, which I find new developers have a hard time understanding.



来源:https://stackoverflow.com/questions/2374963/c-sharp-pass-a-property-by-reference

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!