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



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

我不确定是否有更好的方法可以做到这一点,但我对所有的建议都持开放态度。我已经创建了一个相当通用的工作解决方案,但如果可能的话,我希望它更通用。当前的问题是,我必须拥有相同代码的if/elseif链,只有显式定义类型的一个区别。我不知道如何返回PropertyInfo的GetValue,并将该类型正确地传递到泛型函数中。一旦我取回对象,它总是以"object"而不是"int"、"decimal"等形式传递到Generic中。我还遇到了使用null的装箱/取消装箱问题。我尝试用泛型返回设置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);
    }

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

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

若使用Distinct(),则使用对象比较引用/值类型。第一个测试引用相等的相等,以及后来的实际值。此方法只有一个回调:装箱/取消装箱。将该代码用于引用类型。

注意:在您的代码中已经发生了很多拳击。反射是基于一个"对象"的,所以很难不出现拳击问题。

例如:

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

最新更新