6

为了举例,让我们从一个类定义开始:

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

现在让我们假设我有一个包含 3 个对象的List<Person>调用:people

{"Robby", "Goki", 12, 8}
{"Bobby", "Goki", 10, 8}
{"Sobby", "Goki", 10, 8}

我正在寻找的是某种检索以下单个Person对象的方法:

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

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

我的第一个想法包括:

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;

不幸的是,真正的业务对象的成员多于四个,这既繁琐,又让其他人第一次看到时不知所措。

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

string[] members = new string[] { "FirstName", "LastName", "Age", "Grade" };
foreach (string member in members)
{
    if (people.Select(p => p.**member**).Distinct().Count() == 1)
        unionMan.**member** = people[0].**member**;
}

其中** member **将是但是反射将允许检索和存储该特定成员(假设它是可能的)。

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

4

3 回答 3

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;
}
于 2012-08-02T20:49:11.213 回答
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 访问器都必须是公共的
  • 默认构造函数应定义“无效”值(如@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;
    }
}
于 2012-08-02T20:42:01.583 回答
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);
            }
        }
    }
}

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

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

于 2012-08-02T20:42:15.517 回答