C# 通过引用传递属性

Posted

技术标签:

【中文标题】C# 通过引用传递属性【英文标题】:C# Pass a property by reference 【发布时间】:2011-01-23 09:58:39 【问题描述】:

有没有通过引用传递对象的属性?我知道我可以传递整个对象,但我想指定要设置的对象的属性并检查它的类型,以便我知道如何解析。我是否应该采取另一种方法(无论如何我都无法更改原始对象)?

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

【问题讨论】:

重复***.com/questions/1867708/… 嗯,是的,这个问题的标题是一样的,但是以一种非常令人困惑的方式问这个问题...... 【参考方案1】:

您可以使用相应的方法和委托包装属性并传递委托。

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

【讨论】:

【参考方案2】:

您可以使用 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);
 

你可以这样称呼它:

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

编辑:如果你真的想要类型推断:

private void setFromQueryString<T>(Func<T> getter, Action<T> setter, String queryString, HttpContext context) 
    ...

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

【讨论】:

这看起来很棒。我已经实现了该方法,但是对它的调用给出了“方法'...'的类型参数无法从用法中推断出来。尝试明确指定类型参数。” 这非常接近但它迫使我这样做... setFromQueryString((int i) => myFoo.Age = i, "inputAge", context);我希望它推断类型。 好的,这是有道理的。我需要 getter 能够推断出类型。谢谢。【参考方案3】:

不,没有办法直接通过引用传递属性。 Visual Basic 通过将属性的值放入临时变量中,然后通过引用传递并在返回时重新分配,从而在语言中提供了这种支持。

在 C# 中,您只能通过传递 Func&lt;T&gt; 来获取属性值和传递 Action&lt;T&gt; 来设置值(使用闭包)来近似此行为,其中 T 是属性的类型。

【讨论】:

【参考方案4】:

为什么不使用泛型并返回对象?

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

不太清楚你为什么如此热衷于推理,但鉴于你是,你可以这样做

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

【讨论】:

这是一种方法,但我不想在调用中指定类型“”。那应该从传入的内容中推断出来。 很肯定它会在 C#3 中推断出来 没有与 SLaks 解决方案相同的问题。它不会推断类型。【参考方案5】:

使用 lambda 传递函数可能是最优雅的,但如果您只想简单地解决您的问题

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】:

你为什么把它弄得这么复杂?你在编译时就知道属性的类型,只需一行代码就可以轻松搞定:

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

如果您需要检查类型,只需添加一个包装 int.TryParse() 的小函数,如果您在查询字符串值中得到“pdq”而不是数字,则返回一个无害的结果(例如 0)。

【讨论】:

当然这是正常的做法,但我正在尝试使某些东西更可重用。我想看看有没有办法去除 int.Parse 的冗余以及封装其他一些错误处理逻辑。 我明白这一点,但是您牺牲了相当多的可读性和可维护性,而实际上并没有什么;没有代码重用,因为它是一行代码,而且你并没有让它变得更优雅或更容易编码,IMO。我使用的一个非常优雅的解决方案是为您的网页创建包含查询字符串参数作为属性的类(从 System.Web.UI.Page 派生)并将它们设置在 OnLoad 覆盖中,这样它们就可用就像页面后面的代码中的 this.PropertyName 一样,并在那个谨慎的类中进行类型检查。我会发布另一个答案...【参考方案7】:

正如其他人所指出的,您可以使用委托来执行此操作,使用指定委托的多种方法之一。但是,如果您打算定期执行此操作,则应考虑创建一个包装器类型,用于通过引用传递属性来包装所需的委托,它可能会创建一个更好的 API。

例如:

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;
   

这样您可以传递对您的属性的引用并通过设置引用值来修改它。

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

reference.Value = someNewValue;

【讨论】:

【参考方案8】:

这里有一个完全不同的解决方案:

创建从具有 QueryString 参数作为属性的 System.Web.UI.Page 派生的类。此外,使用实用程序函数(请参阅下面的 ConvertType),您不需要做太多事情来从 QueryString 中获取数据。最后,在这些派生类中,定义一个静态内部类,其中包含作为 QueryString 参数名称的常量,这样您就不需要在任何地方引用任何魔法值。

我通常为我的项目定义一个基本页面类,这使它成为一个方便的地方来执行所有页面上发生的常见事情,以及一些实用功能:

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

然后,在您的常规页面中,在后面的代码中,它现在看起来像这样:

public partial class MyWebPage : MyPage

  protected void Page_Load(object sender, EventArgs e)
  
    Foo myFoo = new Foo();
    Foo.Age = this.Age;
  

它使类背后的代码非常干净(如您所见),并且易于维护,因为所有繁重的工作都由两个函数(GetQueryStringValue 和 ChangeType)完成,每个函数都重用页面类,并且所有内容都是类型安全的(您会在 GetQueryStringValue 中注意到,如果值无法转换或仅使用返回默认值,您可以指定函数是否抛出;两者都适用于不同的时间,具体取决于你的应用程序)。

此外,您甚至可以轻松编写 VS 插件或 CodeSmith 脚本来非常轻松地生成派生页面类。而且没有一堆委托和东西被传递,我发现新开发人员很难理解。

【讨论】:

以上是关于C# 通过引用传递属性的主要内容,如果未能解决你的问题,请参考以下文章

在 C# 中传递数组参数:为啥它是通过引用隐式传递的?

C# 按值传递与按引用传递

通过引用传递值类型而不在c#中初始化[重复]

c# 值传递 引用传递

c# 中是不是默认通过引用传递数组或列表?

当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里到底是值传递还是引用传递?