C # передать свойство по ссылке - PullRequest
23 голосов
/ 04 марта 2010

Есть ли способ передать свойство объекта по ссылке? Я знаю, что могу передать весь объект, но я хочу указать свойство объекта для установки и проверить его тип, чтобы я знал, как анализировать. Должен ли я пойти другим путем (я все равно не могу изменить исходный объект)?

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

Ответы [ 8 ]

18 голосов
/ 04 марта 2010

Вы можете вызвать функцию с лямбда-выражением:

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);
5 голосов
/ 04 марта 2010

Как уже отмечали другие, вы можете сделать это, используя делегатов, используя один из многих способов указать делегатов. Однако, если вы собираетесь делать это регулярно, вам следует подумать о создании типа оболочки для передачи свойств по ссылке, которая обернет необходимые делегаты, это может создать более приятный 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;
5 голосов
/ 04 марта 2010

Нет, нет возможности напрямую передать свойство по ссылке. Visual Basic предлагает эту поддержку в языке, помещая значение свойства в переменную temp, а затем оно передается по ссылке и переназначается при возврате.

В C # вы можете только приблизить это поведение, передав Func<T> для получения значения свойства и Action<T> для установки значения (используя замыкания), где T - это тип свойства.

5 голосов
/ 04 марта 2010

Вы можете обернуть свойство соответствующими методами и делегатами и передать делегаты.

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 голосов
/ 04 марта 2010

Функция прохождения с лямбдой, вероятно, наиболее элегантна, но если вы просто хотите простое решение вашей проблемы

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)
{
...
}
2 голосов
/ 04 марта 2010

Почему бы не использовать дженерики и вернуть объект?

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);
}
1 голос
/ 04 марта 2010

Вот вам совершенно другое решение:

Создание классов, производных от System.Web.UI.Page, которые имеют параметры QueryString в качестве свойств. Кроме того, используя служебную функцию (см. 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, чтобы довольно легко сгенерировать производный класс страницы. И там не будет куча делегатов и прочего, что, как мне кажется, новым разработчикам трудно понять.

1 голос
/ 04 марта 2010

Почему ты делаешь это таким сложным? Вы знаете тип свойства во время компиляции, просто сделайте это простым способом с одной строкой кода:

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

Если вам нужно проверить тип, просто добавьте небольшую функцию, которая оборачивает int.TryParse () и возвращает безобидный результат (например, 0), если вместо числа указано «pdq» в значении строки запроса.

...