Подтвердить что ты не робот

С# Передайте свойство по ссылке

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

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 type
    Type t = aProperty.GetType();
}

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

Ответ 1

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

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

Ответ 2

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

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

Ответ 3

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

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

Ответ 4

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

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

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

Ответ 6

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

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

Ответ 7

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

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

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

Ответ 8

Здесь совершенно другое решение для вас:

Создайте классы, полученные из 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 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 script, чтобы легко создать производный класс. И не хватает кучу делегатов и вещей, которые мне нравятся, и я нахожу, что новые разработчики с трудом понимают.