Как выйти из повторяющихся операторов if?

#algorithm #c#-4.0 #if-statement

#алгоритм #c #-4.0 #if-оператор

Вопрос:

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

 public string DataField(int id, string fieldName)
{
   var data = _dataRepository.Find(id);
   if (data != null)
  {
      if (data.A == null)
      {
        data.A = fieldName;
        _dataRepository.InsertOrUpdate(data);
        return "A";
      }

      if (data.B == null)
      {
        data.B = fieldName;
        _dataRepository.InsertOrUpdate(data);
        return "B";
      }

    // keep going data.C through data.Z  doing the exact same code
  }
}
  

Очевидно, что наличие 26 операторов if только для определения, является ли свойство null, а затем для обновления этого свойства и выполнения вызова базы
данных, вероятно, очень наивно в реализации. Какой был бы лучший способ выполнить эту единицу работы?

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

1. Представляем: codereview.stackexchange.com

2. Корень вашей проблемы — это класс с 26 свойствами, которые называются от A до Z. Есть ли причина, по которой это не массив или словарь или что-то в этом роде?

3. Что именно делает приложение. Я пытаюсь придумать вещи, для которых потребовалось бы 26 свойств алфавита, и все, что я придумал до сих пор, просто для «развлечения».

4. Это не такая уж надуманная проблема. Представьте себе класс, который имеет пару десятков string свойств, и командный интерфейс, который хочет их изменить. Итак, кто-нибудь может ввести команду типа «set name = jim» или «set key = abc123». Это проблема того же типа. Если вы разрабатываете класс одновременно с разработкой командного интерфейса, вы можете написать класс для его поддержки. Но если вы хотите реализовать командный интерфейс после того, как класс уже запущен в производство, у вас очень похожая проблема.

Ответ №1:

К счастью, C # способен динамически проверять и назначать члены класса, поэтому одним из вариантов было бы создание Карта перечислите и повторите это.

     public string DataField(int id, string fieldName)
    {
        var data = _dataRepository.Find(id);

        List<string> props = new List<string>();
        props.Add("A");
        props.Add("B");
        props.Add("C");

        if (data != null)
        {
            Type t = typeof(data).GetType();
            foreach (String entry in props) {
                PropertyInfo pi = t.GetProperty(entry);
                if (pi.GetValue(data) == null) {
                    pi.SetValue(data, fieldName);
                    _dataRepository.InsertOrUpdate(data);
                    return entry;
                }
            }
        }
    }
  

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

1. Хотя я не мог напрямую использовать ваш код, поиск PropertyInfo в объекте заставил нас по-настоящему задуматься о том, что мы делаем. После небольшого анализа кода мы на самом деле решили изменить нашу схему данных и решить нашу проблему совсем по-другому, а не использовать настройки «a», «b», «c»… Спасибо за понимание.

Ответ №2:

Вы могли бы просто перебрать все символы от ‘A’ до ‘Z’. Это становится сложным, потому что вы хотите получить доступ к атрибуту вашего объекта «данные» с соответствующим именем, но это должно (насколько я знаю) быть возможным с помощью функции отражения C #.

Хотя вы избавляетесь от последовательных операторов if, это все равно не сделает ваш код приятным: P

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

1. Это то, чего я боялся, когда люди впервые рассказали мне об отражении… Это похоже на встроенную поддержку плохого стиля: D

2. Полностью согласен, ничто не сделало код красивым, поэтому мы пошли дальше и заглянули немного назад и решили внести некоторые изменения в базовую схему данных.

Ответ №3:

для вашей проблемы есть необычное решение linq с использованием отражения:

но, как было сказано ранее: ваша структура данных не очень хорошо продумана

 public String DataField(int id, string fieldName)
{
    var data = new { Z = "test", B="asd"};
    Type p = data.GetType();

    var value = (from System.Reflection.PropertyInfo fi 
                     in p.GetProperties().OrderBy((fi) => fi.Name)
                     where fi.Name.Length == 1 amp;amp; fi.GetValue(data, null) != null 
                     select fi.Name).FirstOrDefault();
    return value;
}
  

ta taaaaaaaaa
таким образом, вы получаете свойство, но обновление еще не выполнено.

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

1. Мне очень понравился способ linq погружения в свойства объекта и итерации по PropertyInfo каждого элемента. Я пошел дальше и попробовал это, но обнаружил, что наши требования были еще более странными, чем я думал на первый взгляд. Спасибо за понимание, и вы начали новый разговор с нами о переосмыслении нашей схемы данных и о том, как наши классы будут работать друг с другом.

Ответ №4:

 var data = _dataRepository.Find(id);
  

Если возможно, вы должны использовать другой тип данных без этих 26 свойств. Этот новый тип данных должен иметь свойство 1, а метод Find должен возвращать экземпляр этого нового типа данных; тогда вы могли бы избавиться от 26 if более естественным способом.

Чтобы вернуть «A», «B» … «Z», вы могли бы использовать это:

 return (char)65; //In this example this si an "A"
  

И работать с некоторым преобразованием данных.Значение равно числу от 65 до 90 (от А до Я).

Ответ №5:

Поскольку вы всегда сначала устанавливаете поле с наименьшим алфавитом и возвращаете значение, вы можете использовать дополнительное поле в своем классе, которое отслеживает первое доступное поле. Например, это может быть целое число lowest_alphabet_unset , и вы будете обновлять его всякий раз, когда задаете данные.{X}:

Инициализация:

 lowest_alphabet_unset = 0;
  

В поле данных:

 lowest_alphabet_unset   ;
switch (lowest_alphabet_unset) {

    case 1: 
        /* A is free */
        /* do something */
        return 'A';
    [...]

    case 7:
        /* A through F  taken */
        data.G = fieldName;
        _dataRepository.InsertOrUpdate(data);
        return 'G';
    [...]
}
  

Ответ №6:

Примечание: — не используйте, если данные являются объектом, а не структурой.

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

 start = amp;data;

for (i = 0; i < 26; i  ){
   if ((typeof_elem) *(start   sizeof(elem)*i) != null){
      *(start   sizeof(elem)*i) = fieldName;
      return (char) (65   i);
   }
}
  

не проверено, но дает представление 😉

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

1. это рискованно, но если все сделано правильно, будет работать бесперебойно. хотя, если данные представляют собой объект, а не структуру, я должен признать, что это был бы не очень хороший путь.

2. Среди прочего, это предполагает, что поля расположены по порядку в объекте (или структуре). Для этого также требуется небезопасный код (который вы не показываете). И, как вы говорите, это не будет хорошо работать, если data это ссылочный тип (что вполне вероятно).

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