基于成员平等的两个对象联盟

时间:2012-08-02 20:16:08

标签: c#

让我们从一个类定义开始,例如:

public class Person
{
    public string FirstName;
    public string LastName;
    public int Age;
    public int Grade;
}

现在让我们假设我有一个名为List<Person>的{​​{1}}包含3个对象:

people

我正在寻找的是检索以下单个{"Robby", "Goki", 12, 8} {"Bobby", "Goki", 10, 8} {"Sobby", "Goki", 10, 8} 对象的一些方法:

Person

其中所有对象中相同的字段保留其值,而具有多个值的字段将替换为某些无效值。

我的第一个想法包括:

{null, "Goki", -1, 8}

不幸的是,真实的商业对象有多于4个成员,这对于写作而言非常繁琐,并且让其他人第一次看到它们的压力很大。

我还考虑过以某种方式利用反射将这些重复的检查和分配放在一个循环中:

Person unionMan = new Person();
if (people.Select(p => p.FirstName).Distinct().Count() == 1)
    unionMan.FirstName = people[0].FirstName;
if (people.Select(p => p.LastName).Distinct().Count() == 1)
    unionMan.LastName = people[0].LastName;
if (people.Select(p => p.Age).Distinct().Count() == 1)
    unionMan.Age = people[0].Age;
if (people.Select(p => p.Grade).Distinct().Count() == 1)
    unionMan.Grade = people[0].Grade;

** 成员 ** 然后反射将允许检索和存储该特定成员(假设有可能)。

虽然第一个解决方案可行,而我假设的第二个解决方案可行,但有没有人有更好的替代解决方案来解决这个问题?如果不是,使用上述反射是否可行?

3 个答案:

答案 0 :(得分:5)

为了计算不同的成员,做一个独特的所有值是没有效率的。您有一个快捷方案,其中在任何中找到与第一个项目成员的值不同的后续项目中的一个值意味着您对该列的状态无效。

这样的东西应该可行,但是如果任何成员是数组,需要做更多的工作,需要递归评估或其他更复杂的逻辑(注意我没有测试过这个):

public static T UnionCombine<T>(this IEnumerable<T> values) where T : new() {
    var newItem = new T();
    var properties = typeof(T).GetProperties();
    for (var prop in properties) {
        var pValueFirst = prop.GetValue(values.First(), null);
        var useDefaultValue = values.Skip(1).Any(v=>!(Object.Equals(pValueFirst, prop.GetValue(v, null))));
        if (!useDefaultValue) prop.SetValue(newItem, pValueFirst, null);
    }
    return newItem;
}

答案 1 :(得分:2)

你的最后一个想法对我来说似乎很好,就像这样:

List<Person> persons = new List<Person>()
{
    new Person(){ FirstName="Robby", LastName="Goki", Age=12, Grade=8},
    new Person(){ FirstName="Bobby", LastName="Goki", Age=10, Grade=8},
    new Person(){ FirstName="Sobby", LastName="Goki", Age=10, Grade=8},
};

var properties = typeof(Person).GetProperties();

var unionMan = new Person();
foreach (var propertyInfo in properties)
{
    var values = persons.Select(x => propertyInfo.GetValue(x, null)).Distinct();
    if (values.Count() == 1)
        propertyInfo.SetValue(unionMan, propertyInfo.GetValue(persons.First(), null), null);
}

有几点意见:

  • 您的班级成员应该被定义为属性而不是公共成员 并且get和set accessor必须是公共的
  • 默认构造函数应该定义“无效”值(正如@RaphaëlAlthaus正确建议的那样)

所以,Person类看起来像这样:

public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }
    public int Grade { get; set; }
    public Person()
    {
        this.FirstName = null;
        this.LastName = null;
        this.Age = -1;
        this.Grade = -1;
    }
}

答案 2 :(得分:1)

更新:由于您无法控制Person类,并且在公共字段而不是属性中定义了状态,因此我更新了解决方案以解决此问题。

我建议使用反射。您可能希望提前获取FieldInfo(或PropertyInfo)对象,而不是为LINQ查询中的每个条目获取它。您可以使用Type.GetFieldType.GetProperty来获取它们。完成后,您只需使用FieldInfo / PropertyInfo.GetValueFieldInfo / PropertyInfo.SetValue

例如:

Type personType = typeof(Person);
foreach(string member in members)
{   // Get Fields via Reflection
    FieldInfo field = peopleType.GetField(member);
    if(field != null)
    {
        if (people.Select(p => field.GetValue(p, null) ).Distinct().Count() == 1)
        {
            field.SetValue(unionMan, field.GetValue(people[0], null), null);
        }
    }
    else // If member is not a field, check if it's a property instead
    {   // Get Properties via Reflection
        PropertyInfo prop = peopleType.GetProperty(member);
        if(prop != null)
        {
            if (people.Select(p => prop.GetValue(p, null) ).Distinct().Count() == 1)
            {
                prop.SetValue(unionMan, prop.GetValue(people[0], null), null);
            }
        }
    }
}

正如您所指出的,您已经在默认构造函数中设置了“无效”值,因此您不必在此循环中担心它们。

注意:在我的示例中,我使用了不BindingFlags参数的GetFieldGetProperties版本。这些只会返回公共成员。