获取一系列对象中所有属性的默认值的通用解决方案

时间:2013-12-12 21:09:35

标签: c# .net linq generics reflection

我想要取回一系列对象中所有属性的默认值。使用的逻辑是,如果范围中的所有属性值都相同,则使用该值作为默认值,否则将其保留为null / type default。

我不确定是否有更好的方法可以做到这一点,但我对所有建议持开放态度。我已经创建了一个相当通用的工作解决方案,但是如果可能的话,我希望它更加如此。当前的问题是我必须使用相同代码的if / elseif链,只有明确定义类型的一个区别。我无法弄清楚如何取回PropertyInfo的GetValue并将类型正确地传递给泛型函数。一旦我得到了对象,它总是会传递给Generic作为'object'而不是'int','decimal'等。我也遇到了nullables的装箱/拆箱问题。我尝试使用泛型返回设置GetPropertyValue函数,但是这需要您传入类型,因为我在函数内部将其传入,所以我没有这样做。

所有这些代码只是一个有效的例子。我的类有数百个属性,有30个不同的类,大约有3000个属性,我不想明确写出来。

public class MainFunction
{
    public MainFunction()
    {
        ParentClass defaultClass = new ParentClass();

        List<ParentClass> results = MyDatabaseCallThatGetsBackListOfClass();

        defaultClass = Generic.GetDefaultProperty(defaultClass, results);
    }

    private List<ParentClass> MyDatabaseCallThatGetsBackListOfClass()
    {
        List<ParentClass> populateResults = new List<ParentClass>();
        for (int i = 0; i < 50; i++)
        {
            populateResults.Add(new ParentClass()
            {
                Class1 = new SubClass1()
                {
                    Property1 = "Testing",
                    Property2 = DateTime.Now.Date,                        
                    Property3 = true,
                    Property4 = (decimal?)1.14,
                    Property5 = (i == 1 ? 5 : 25), // different, so should return null
                    Class1 = new SubSubClass1()
                    {
                        Property1 = "Test"
                    },
                    Class2 = new SubSubClass2()
                },
                Class2 = new SubClass2()
                {
                    Property1 = null,
                    Property2 = 10,
                    Property3 = (i == 1 ? 15 : 30), // different, so should return null
                    Property4 = 20
                }
            });
        }
        return populateResults;
    }
}

public class ParentClass
{
    public ParentClass()
    {
        this.Class1 = new SubClass1();
        this.Class2 = new SubClass2();
    }

    public SubClass1 Class1 { get; set; }
    public SubClass2 Class2 { get; set; }
}

public class SubClass1
{
    public SubClass1()
    {
        this.Class1 = new SubSubClass1();
        this.Class2 = new SubSubClass2();
    }

    public string Property1 { get; set; }
    public DateTime? Property2 { get; set; } 
    public bool? Property3 { get; set; }
    public decimal? Property4 { get; set; } 
    public int? Property5 { get; set; }

    public bool Property6 { get; set; }
    public decimal Property7 { get; set; }
    public DateTime Property8 { get; set; }
    public int Property9 { get; set; }

    public SubSubClass1 Class1 { get; set; }
    public SubSubClass2 Class2 { get; set; }
}

public class SubClass2
{
    public int? Property1 { get; set; }
    public int? Property2 { get; set; }
    public int Property3 { get; set; }
    public int Property4 { get; set; }
}

public class SubSubClass1
{
    public string Property1 { get; set; }
    public string Property2 { get; set; }
}

public class SubSubClass2
{
    public decimal? Property1 { get; set; }
    public decimal Property2 { get; set; }
}

public static class Generic
{
    public static T GetDefaultProperty<T>(T defaultItem, List<T> itemList)
        where T : class
    {
        Type defaultType = defaultItem.GetType();
        var props = defaultType.GetProperties(BindingFlags.Instance | BindingFlags.Public).Where(p => p.CanRead);
        foreach (var p in props)
        {
            if (p.PropertyType.IsClass && p.PropertyType != typeof(string))
            {
                dynamic classProperty = GetPropertyValue(defaultItem, p.Name);
                var classList = GetClassSubList(itemList, classProperty, p.Name);
                p.SetValue(defaultItem, GetDefaultProperty(classProperty, classList), null);
            }
            else
            {
                if (p.PropertyType == typeof(int?))
                {
                    List<int?> subList = GetPropertySubList(itemList, TypeDefault<int?>(), p.Name);
                    if (subList.Distinct().ToList().Count == 1)
                    {
                        p.SetValue(defaultItem, subList.FirstOrDefault(), null);
                    }
                }
                else if (p.PropertyType == typeof(bool?))
                {
                    List<bool?> subList = GetPropertySubList(itemList, TypeDefault<bool?>(), p.Name);
                    if (subList.Distinct().ToList().Count == 1)
                    {
                        p.SetValue(defaultItem, subList.FirstOrDefault(), null);
                    }
                }
                else if (p.PropertyType == typeof(decimal?))
                {
                    List<decimal?> subList = GetPropertySubList(itemList, TypeDefault<decimal?>(), p.Name);
                    if (subList.Distinct().ToList().Count == 1)
                    {
                        p.SetValue(defaultItem, subList.FirstOrDefault(), null);
                    }
                }
                else if (p.PropertyType == typeof(DateTime?))
                {
                    List<DateTime?> subList = GetPropertySubList(itemList, TypeDefault<DateTime?>(), p.Name);
                    if (subList.Distinct().ToList().Count == 1)
                    {
                        p.SetValue(defaultItem, subList.FirstOrDefault(), null);
                    }
                }
                else if (p.PropertyType == typeof(string))
                {
                    List<string> subList = GetPropertySubList(itemList, TypeDefault<string>(), p.Name);
                    if (subList.Distinct().ToList().Count == 1)
                    {
                        p.SetValue(defaultItem, subList.FirstOrDefault(), null);
                    }
                }
                else if (p.PropertyType == typeof(int))
                {
                    List<int> subList = GetPropertySubList(itemList, TypeDefault<int>(), p.Name);
                    if (subList.Distinct().ToList().Count == 1)
                    {
                        p.SetValue(defaultItem, subList.FirstOrDefault(), null);
                    }
                }
                else if (p.PropertyType == typeof(bool))
                {
                    List<bool> subList = GetPropertySubList(itemList, TypeDefault<bool>(), p.Name);
                    if (subList.Distinct().ToList().Count == 1)
                    {
                        p.SetValue(defaultItem, subList.FirstOrDefault(), null);
                    }
                }
                else if (p.PropertyType == typeof(decimal))
                {
                    List<decimal> subList = GetPropertySubList(itemList, TypeDefault<decimal>(), p.Name);
                    if (subList.Distinct().ToList().Count == 1)
                    {
                        p.SetValue(defaultItem, subList.FirstOrDefault(), null);
                    }
                }
                else if (p.PropertyType == typeof(DateTime))
                {
                    List<DateTime> subList = GetPropertySubList(itemList, TypeDefault<DateTime>(), p.Name);
                    if (subList.Distinct().ToList().Count == 1)
                    {
                        p.SetValue(defaultItem, subList.FirstOrDefault(), null);
                    }
                }
            }
        }
        return defaultItem;
    }

    private static object GetPropertyValue<T>(T item, string propertyName)
    {
        if (item == null || string.IsNullOrEmpty(propertyName))
        {
            return null;
        }
        PropertyInfo pi = item.GetType().GetProperty(propertyName);
        if (pi == null)
        {
            return null;
        }
        if (!pi.CanRead)
        {
            return null;
        }
        return pi.GetValue(item, null);
    }

    private static List<TReturn> GetClassSubList<T, TReturn>(List<T> list, TReturn returnType, string propertyName)
        where T : class
        where TReturn : class
    {
        return list.Select(GetClassSelection<T, TReturn>(propertyName)).ToList();
    }

    private static Func<T, TReturn> GetClassSelection<T, TReturn>(string fieldName)
        where T : class
        where TReturn : class
    {
        ParameterExpression p = Expression.Parameter(typeof(T), "t");
        var body = Expression.Property(p, fieldName);
        return Expression.Lambda<Func<T, TReturn>>(body, new ParameterExpression[] { p }).Compile();
    }

    private static List<TReturn> GetPropertySubList<T, TReturn>(List<T> list, TReturn returnType, string propertyName)
        where T : class
    {
        return list.Select(GetPropertySelection<T, TReturn>(propertyName)).ToList();
    }

    private static Func<T, TReturn> GetPropertySelection<T, TReturn>(string fieldName)
        where T : class
    {
        ParameterExpression p = Expression.Parameter(typeof(T), "t");
        var body = Expression.Property(p, fieldName);
        return Expression.Lambda<Func<T, TReturn>>(body, new ParameterExpression[] { p }).Compile();
    }

    private static T TypeDefault<T>()
    {
        return default(T);
    }

1 个答案:

答案 0 :(得分:2)

您可以使用以下方法切换巨大的IF语句块:

var result = itemList.Select(x => p.GetValue(x, null)).Distinct();
if (result.Count() == 1)
{
    p.SetValue(defaultItem, result.First(), null);
}

如果使用Distinct(),则使用首先测试引用相等性的object.Equals和稍后的实际值来比较引用/值类型。这种方法只有一个缺点:装箱/拆箱。将该代码用于参考类型。

注意:你的代码中已经发生了很多拳击事件。 反思基于“对象”,所以很难不发生拳击问题。

例如:

Type defaultType = defaultItem.GetType(); // boxing on value types.
p.SetValue(defaultItem, subList.FirstOrDefault(), null); // boxing

拳击是反射的次要成本。您可以运行基准来检查。

至于你的实际问题;你有对象列表,你想要递归地比较它们。如果两个对象之间没有区别,则需要将defaultItem中的属性设置为所有对象共享的属性值。

忽略你之所以这样做的原因(因为我不在乎;相反,这个问题的解决方案很有意思),让我们继续:P

您最好的选择是在启动时使用反射生成强类型比较器。使用StringBuilder生成代码,稍后使用CSharpCodeProvider()从StringBuilder进行编译,并返回没有反射开销的强类型委托。这是我能想到的最快的一个。它唯一需要的就是在启动时首先查询反射元数据。这只是T。

在生产代码上,您可以将强类型比较器缓存到DLL上,因此命中只会是一次性事件。

private static class StrongClassComparer<T>
{
    public static Func<T, string> GenerateMethod()
    {
        var code = new StringBuilder();

        // generate your STRONGLY-TYPED code here.
        // into code variable.
        code.Append("public class YourClassInCode { "+
             " public static string YourClassStaticMethod("+ typeof(T).Name+ " test)" + 
               " { return string.Empty; } }");

        var compiler = new CSharpCodeProvider();
        var parameters = new CompilerParameters();
        parameters.ReferencedAssemblies.Add(typeof (T).Assembly.Location);
        parameters.CompilerOptions = "/optimize + ";

        var results = compiler.CompileAssemblyFromSource(parameters, code.ToString());
        var @class = results.CompiledAssembly.GetType("YourClassInCode");
        var method = @class.GetMethod("YourClassStaticMethod");

        return (Func<T, string>) Delegate.CreateDelegate(typeof (Func<T, string>), method);
    }
}