【问题标题】:C# Pass a property by referenceC# 通过引用传递属性
【发布时间】: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);
}

【问题讨论】:

标签: c# .net properties pass-by-reference


【解决方案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 脚本来非常轻松地生成派生页面类。而且没有一堆委托和东西被传递,我发现新开发人员很难理解。

          【讨论】:

            猜你喜欢
            • 2010-11-27
            • 1970-01-01
            • 2010-12-24
            • 2018-07-09
            • 1970-01-01
            • 1970-01-01
            • 2014-11-08
            相关资源
            最近更新 更多