Как отфильтровать коллекцию внутри универсального метода

У меня есть два класса, который имеет следующие свойства

 Class A
  {
      public int CustID { get; set; }
      public bool isProcessed { get; set; }
  }
  Class B
  {
      public int EmpId{ get; set; }
      public bool isProcessed { get; set; }
  }

Я создал один универсальный метод, который принимает все эти классы. Свойство isProcessed является общим для обоих этих классов.

public void ProceesData<T>(IList<T> param1, string date1)
{

}

Мне нужны следующие вещи

  1. Внутри метода ProcessData я хочу отфильтровать элементы, для которых флаг isProcessed имеет значение "True".
  2. Также я хочу повторить эту коллекцию и нужно установить значения для свойства IsProcessed.

Примечание: я предпочитаю решение с использованием отражения, так как имя свойства является константой (то есть "IsProcessed")

Может ли кто-нибудь помочь в этом.

3 ответа

Самый простой способ - убедиться, что оба класса реализуют общий интерфейс и ограничивают ваш общий метод. Например:

public interface IProcessable
{
    bool isProcessed { get; set; }
}
public class A : IProcessable
{
    public int CustID { get; set; }
    public bool isProcessed { get; set; }
}

public class B : IProcessable
{
    public int EmpId { get; set; }
    public bool isProcessed { get; set; }
}

Теперь ваш метод будет выглядеть так:

public void ProceesData<T>(IList<T> param1, string date1)
    where T : IProcessable // <-- generic constraint added
{
    foreach (var element in param1)
    {
        element.isProcessed = true;
    }
}

Другой вариант, который более полезен, если вы не можете использовать интерфейс или имена свойств могут отличаться, заключается в передаче Action<T> в качестве параметра для вашего метода. Например:

public void ProceesData<T>(IList<T> param1, string date1, Action<T> func)
{
    foreach (var element in param1)
    {
        func(element);
    }
}

И назовите это так:

ProceesData<A>(list, "", x => x.isProcessed = true);

Создать интерфейс, как IProcessData, который содержит логическое свойство, IsProcessed, Пусть оба класса реализуют этот интерфейс. Измени свой ProcessData метод, так что он больше не имеет общего обозначения (<T>) и принимает IList<IProcessData>, Затем выполните фильтрацию и итерации данных param1.

Примечание: я предпочитаю решение с использованием отражения

Этот метод будет повторять коллекцию, фильтровать в соответствии с propertyName а также filterValue и установите значения в newValue используя отражение:

public void ProceesData<T>(IList<T> param1, string date1, string propertyName, 
                                  object filterValue, object newValue)
{
    PropertyInfo pi = typeof(T).GetProperty(propertyName);

    object value;

    for (int i = param1.Count; i <= 0; i--)
    {
        value = pi.GetValue(param1[i]);
        if (value.Equals(filterValue))
        {
            pi.SetValue(param1[i], newValue);
        }
    }
}

Вы можете назвать это так:

ProceesData<A>(a_list, "", "isProcessed", false, true);

Отказ от ответственности:

Хотя это возможно. Это далеко не сохранить. Если вы передадите неправильное имя свойства, это не удастся! Я бы порекомендовал использовать второй подход, передав @DavidG Action делегировать. Это сделает всю обработку более надежной и менее подверженной ошибкам. Я бы предложил использовать здесь обычный обратный цикл for, потому что это даже позволит вам удалить элементы из вашей коллекции.

public static void ProceesData<T>(IList<T> param1, string date1, Action<T> func)
{            
    for (int i = param1.Count; i <= 0; i--)
    {
        func(param1[i]);
    }
}

Этот вызов даст вам тот же результат:

ProceesData<A>(a_list, "", (x)=> { if (!x.isProcessed) x.isProcessed = true; });

Благодаря этому подходу вы становитесь еще более гибкими, поскольку при каждом вызове вы можете решить, что должен делать этот метод. Вы даже можете удалить обработанные предметы из коллекции:

ProceesData<A>(a_list, "", (x)=> { if (!x.isProcessed) a_list.Remove(x); });

Одно отличие остается, хотя. Потому что, если у вас есть коллекция, которая будет содержать оба элемента A а также B как это: (я использовал примерный конструктор для этого случая)

List<object> obj_list = new List<object>()
{
    new A(1, false),
    new B(2, true),
    new A(3, false),
    new B(4, false),
};

Вы должны будете использовать dynamic тип данных для Action перегрузка:

ProceesData<dynamic>(obj_list, "", (x) => { if (!x.isProcessed) obj_list.Remove(x); });

И для того, чтобы сделать способ отражения, вам нужно будет проверять тип на каждой итерации. Это увеличит время обработки.

public static void ProceesData<T>(IList<T> param1, string date1, string propertyName, object filterValue, object newValue)
{
    for (int i = param1.Count-1; i >= 0; i--)
    {
        PropertyInfo pi = param1[i].GetType().GetProperty(propertyName);
        object value;

        value = pi.GetValue(param1[i]);
        if (value.Equals(filterValue))
        {
            pi.SetValue(param1[i], newValue);
        }
    }
}
Другие вопросы по тегам