Динамическое назначение полей

#c# #.net #reflection #collections

#c# #.net #отражение #Коллекции

Вопрос:

У меня есть набор свойств следующим образом:

 public string Foo1 {set;get;}
public string Foo2 {set;get;}
public string Foo3 {set;get;}
public string Foo4 {set;get;}
public string Foo5 {set;get;}
public string Foo6 {set;get;}
public string Foo7 {set;get;}
public string Foo8 {set;get;}
public string Foo9 {set;get;}
......
public string Foo50 {set;get;}
  

затем я перебираю коллекцию следующим образом:

 foreach(var element in sortedCollection.Keys){
   if(element != null)
   // in this block I would like to assign the element to the properties above
   // ex:
   foo?? = sortedCollection[element];
   // ?? need to be replaced by index.
}
  

Есть ли простой способ сделать это?

Комментарии:

1. Есть ли причина для такого дизайна класса?

2. он уже разработан таким образом. не могу это изменить.

3. Вероятно, позже вы столкнетесь с большими проблемами, если будете использовать рефлексию, а не рефакторинг для улучшения дизайна… (После добавления отражения становится очень сложно его реорганизовать / перепроектировать)

Ответ №1:

Я думаю, что лучший дизайн был бы:

 public List<string> Foos { get; private set; }
  

Если вы не можете это изменить, вы, вероятно, могли бы сделать что-то вроде:

 var type = typeof(MyCalss);
int index = 1;
foreach (var key in sortedCollection.Keys)
{
   var value = sortedCollection[key];
   var prop = type.GetProperty("Foo"   index);
   if (prop != null) prop.SetValue(this, value, null);

   index  ;
}
  

… конечно, с некоторой обработкой ошибок, и где this предполагается, что это метод внутри вашего класса. Можете ли вы определить индекс на основе значений в вашем sortedCollection ?

Комментарии:

1. Ну, в качестве обходного пути вы могли бы создать индексатор, который использовал бы отражения для доступа к свойствам Foo . Это, по крайней мере, выглядело бы лучше.

2. @user177883: Вы должны возразить тому, кто говорит, что вы не можете улучшить дизайн — исправление плохого дизайна, подобного этому, почти всегда лучше делать раньше, а не позже.

3. Как я уже упоминал, я не могу изменить дизайн. и мне это тоже не нравится. вздох.

Ответ №2:

вы можете использовать отражение и делать это в цикле:

 for ( int i = 1 ; i < 51 ; i  )
  this.GetType().GetProperty(string.Format("Foo{0}",i)).SetValue(this,desiredValue,null);
  

но я думаю, что я рекомендую использовать индексаторы

http://msdn.microsoft.com/en-us/library/2549tw02(v=vs.80).aspx

Ответ №3:

Вы можете делать то, что хотите, с помощью:

  1. Использование цикла for вместо foreach . Таким образом, вы можете работать с текущим индексом.
  2. Использование отражений. Вы можете получить список свойств для своего класса и получить к ним доступ динамически. Например, см. раздел Тип.Получить свойства.

Но почему бы вам просто не использовать a List<string> Foos вместо множества свойств?

Ответ №4:

Вы должны использовать отражение.

это.GetType().getProperty(«Foo» i).setValue(это, SortedCollection[элемент], null);

Однако есть две вещи:

  • Стоимость getProperty не равна нулю. Поэтому, если вы делаете это много раз, вы можете сохранить результат getProperty в каком-либо поле, а затем использовать это поле в вашем foreach .
  • Если ваши свойства действительно называются Something1, Something2 и т. Д., То у вас может быть недостаток дизайна, который вы, возможно, захотите исправить, прежде чем делать это (замените все ваши строковые элементы одним списком).

Ответ №5:

Вам нужно использовать отражение (тип.getProperty()) для получения свойства и установки его значения.

Предполагается, что свойства определены в классе с именем MyClass:

 foreach(var element in sortedCollection.Keys){
   if(element != null)
   // in this block I would like to assign the element to the properties above
   // ex:
   //foo?? = sortedCollection[element];
   // not sure how you are getting the index here, may be you need to use for loop
   PropertyInfo pi = typeof(MyClass).GetProperty("Foo"   index);

   // ?? need to be replaced by index.
   if (pi != null)
   {
       pi.SetValue(<object of MyClass>, sortedCollection[element], null);
   }
}
  

Ответ №6:

 void Main()
{
    var foo = new Foo();
    foo[1] = "Foo1";    
    //foo.Dump();
}

public class Foo
{
    public string Foo1 {set;get;}
    public string Foo2 {set;get;}
    public string Foo3 {set;get;}
    public string Foo4 {set;get;}
    public string Foo5 {set;get;}
    public string Foo6 {set;get;}
    public string Foo7 {set;get;}
    public string Foo8 {set;get;}
    public string Foo9 {set;get;}

    public string this[int index]
    {
        get
        {
            return getPropertyValue(index);
        }
        set
        {
            setPropertyValue(index, value);
        }
    }
    private void setPropertyValue(int i, string value)
    {
       var propi = this.GetType().GetProperty("Foo"   i);
       if (propi != null)
          propi.SetValue(this,value,null);
    }
    private string getPropertyValue(int i)
    {
        var propi = this.GetType().GetProperty("Foo"   i);
        if (propi != null)
            return (string)propi.GetValue(this, null);
        return null;
    }
}
  

Ответ №7:

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

Однако, чтобы предложить что-то другое, вы могли бы изменить класс, содержащий свойства Foo *, чтобы каждый получатель / установщик считывался из индексированного списка:

   public class FooOfDoom
  {
    public string[] Foos = new string[2];

    public string Foo1
    {
      set { Foos[0] = value; }
      get { return Foos[0]; }
    }

    public string Foo2
    {
      set { Foos[1] = value; }
      get { return Foos[1]; }
    }

  }
  

Тогда ваш класс на самом деле не изменится, насколько его контракт со всем остальным кодом, поскольку свойства все еще существуют, но теперь вы можете назначить право на Foos , а не через каждое отдельное свойство.

Опять же, на самом деле я бы использовал DynamicMethod, если бы делал это сам.

Ответ №8:

Лично я не согласен с большинством других плакатов здесь. Я думаю, что использование отражения должно быть ограничено теми ситуациями, когда это действительно требуется (проверка объектов, определенные ситуации с графическим интерфейсом и т. Д.). В этом случае, набрав немного больше текста, можно написать строго типизированную программу и при этом делать то, что вы хотите. Я предложу две альтернативы. Обе альтернативы обеспечат возможность доступа к вашим свойствам как по имени, так и по индексу.

В первом варианте я предполагаю, что нам разрешено изменять определение ваших свойств. Во втором варианте я предполагаю, что эти определения должны оставаться неизменными.

Первый вариант перемещает данные в отдельный массив, добавляет вспомогательные методы для доступа к данным по индексу и изменяет свойства для использования вспомогательных методов:

 private class Version1 {
  private readonly string[] underlyingData=new string[50];

  public string Foo1 { get { return ReadFoo(1); } set { SetFoo(1, value); } }
  public string Foo2 { get { return ReadFoo(2); } set { SetFoo(2, value); } }
  public string Foo3 { get { return ReadFoo(3); } set { SetFoo(3, value); } }
  //......
  public string Foo50 { get { return ReadFoo(50); } set { SetFoo(50, value); } }

  private string ReadFoo(int index) {
    return underlyingData[index-1]; //1-based indexing
  }

  private void SetFoo(int index, string value) {
    underlyingData[index-1]=value; //1-based indexing
  }
}
  

Вторая альтернатива оставляет определения свойств неизменными и два статических массива делегатов, представляющих функции чтения и записи этих свойств.

 private class Version2 {
  private static readonly Func<Version2, string>[] readers=new Func<Version2, string>[] {
    c => c.Foo1,
    c => c.Foo2,
    c => c.Foo3,
    //......
    c => c.Foo50,
  };

  private static readonly Action<Version2, string>[] writers=new Action<Version2, string>[] {
    (c,v) => c.Foo1=v,
    (c,v) => c.Foo2=v,
    (c,v) => c.Foo3=v,
    //......
    (c,v) => c.Foo50=v,
  };

  public string Foo1 { set; get; }
  public string Foo2 { set; get; }
  public string Foo3 { set; get; }
  //......
  public string Foo50 { set; get; }

  private string ReadFoo(int index) {
    return readers[index-1](this); //1-based indexing
  }

  private void SetFoo(int index, string value) {
    writers[index-1](this, value); //1-based indexing
  }
}