How to filter a collection inside a generic method

杀马特。学长 韩版系。学妹 提交于 2019-12-10 11:48:56

问题


I have a two class which is having following properties

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

I created one generic method which accepts all these classes.'isProcessed' property is common in both these classes.

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

}

I need following things

  1. Inside ProcessData method i want to filter items which is having isProcessed flag is "True".
  2. Also i want to iterate this collection and need to set values for IsProcessed property.

Note: I am preferring solution using reflection,since property name is constant (ie "IsProcessed")

Can anyone help on this.


回答1:


The easiest way is to ensure that both classes implement a common interface and constrain your generic method. For example:

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

Now your method would look like this:

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

Another option which is more useful if you cannot use an interface or the property names vary, is to pass in an Action<T> as a parameter to your method. For example:

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

And call it like this:

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



回答2:


Create an interface, like IProcessData, that contains a Boolean property, IsProcessed. Have both classes implement this interface. Change your ProcessData method so that it no longer has the generic notation (<T>) and accepts an IList<IProcessData>. Then perform your filtering and iterations on the param1 data.




回答3:


Note: I am preferring solution using reflection

This method will iterate the collection, filter according to propertyName and filterValue and set the values to the newValue using reflection:

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

You can call it like this:

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

Disclaimer:

Although this is possible. It is far from being save. If you hand over the wrong property name it will fail! I would recommend to use the second approach by @DavidG handing over an Action delegate. This will make the entire processing more sound and less prone to error. I would suggest to use here a normal reverse for-loop, because this would even allow you to delete items from you collection.

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

This call will give you the same result:

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

You get even more flexible by this approach because you can decide at each call what this method is supposed to do. You can even remove the processed items from the collection:

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

One difference remains though. Because if you would have a collection which would contain both elements A and B like this: (I used an exemplary constructor for this case)

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

You would have to use the dynamic data type for the Action overload:

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

And for the reflection way of doing things you would need to check at each iteration the type. This would increase processing time.

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


来源:https://stackoverflow.com/questions/42492382/how-to-filter-a-collection-inside-a-generic-method

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!