2012-08-02 8 views
6

Lassen Sie uns mit einer Klassendefinition zum Wohle Beispiel beginnen:Union von zwei auf Gleichheit ihrer Mitglieder Basierend Objekte

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

Nun nehmen wir an, ich habe eine List<Person> genannt people 3 Objekte enthält:

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

Was ich suche ist eine Möglichkeit, die folgenden Einzel Person Objekt abzurufen:

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

wobei Felder, die in allen Objekten gleich sind, ihren Wert beibehalten, während Felder mit mehreren Werten durch einige ungültige Werte ersetzt werden.

Mein erster Gedanke bestand aus:

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; 

Leider ist das reale Business-Objekt hat viele mehr Mitglieder als vier und dies ist sowohl mühsam zu schreiben und überwältigend für jemand anderen zum ersten Mal zu sehen.

ich auch irgendwie die Nutzung der Reflexion zu setzen diese sich wiederholende Prüfungen und Aufgaben in einer Schleife betrachtet:

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

wo ** Mitglied ** wäre jedoch Reflexion erlauben würde, das Abrufen und Speichern von diesem bestimmten Mitglied (vorausgesetzt, es ist möglich).

Während die erste Lösung funktionieren würde, und die zweite, die ich annehme, würde funktionieren, hat jemand eine bessere alternative Lösung für dieses Problem? Wenn nicht, wäre die Verwendung der oben beschriebenen Reflexion möglich?

+1

Well sollte die Idee deiner Sekunde funktionieren, aber wirklich nicht mit dem Code gezeigt! Übrigens sollten Sie einen Standardkonstruktor für Person mit Ihren "ungültigen" Werten haben. –

Antwort

5

Es ist ineffizient, alle Werte zu unterscheiden, nur um die einzelnen Elemente zu zählen. Sie haben ein Verknüpfungsszenario, in dem die Suche nach einem Wert in einer beliebigen der nachfolgenden Elemente, die nicht denselben Wert wie das Element des ersten Elements aufweist, bedeutet, dass Sie einen ungültigen Status für diese Spalte haben.

So etwas sollte funktionieren, wenn auch mehr Arbeit getan werden müsste, wenn eine der Mitglieder sind Arrays, müssen rekursive Auswertung oder andere komplexere Logik (man beachte ich nicht getestet haben):

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

+1 Großartige Idee, nicht zu verwenden, sondern nur auf den ersten Wert prüfen :) – digEmAll

+0

+1 Sehr beliebt. Sie haben nur vergessen, das Wort 'this' zu Ihrer Parameterdeklaration hinzuzufügen. –

+0

@jmh_gr danke, behoben –

2

Ihre letzte Idee scheint mir gut, so etwas wie dieses:

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

Ein paar Beobachtungen:

  • Ihre Klasse-Mitglieder sollten als Eigenschaften definiert werden und nicht öffentlich Mitglieder und beide bekommen und Set-Accessor muss öffentlich sein
  • der Standardkonstruktor sollte die "ungültigen" Werte definieren (wie von @ RaphaëlAlthaus richtig vorgeschlagen)

so würde die Klasse Person wie folgt aussehen:

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

Großartiger Punkt für den Standardkonstruktor. Gibt es einen Grund, 'PropertyInfo.GetGetMethod()' und 'PropertyInfo.GetSetMethod()' über 'PropertyInfo.GetValue()' und 'PropertyInfo.SetValue()'? Wenn nicht, denke ich, dass Letzteres etwas weniger ausführlich ist. –

+0

@JonSenchyna: Einfach habe ich diese shortcuts vergessen: P Danke für den Hinweis;) – digEmAll

+0

Ah ja, das ist einfach eine Beispielklasse, die ich zusammen in den SO-Editor geworfen habe, das reale Objekt hat tatsächlich einen Konstruktor, der jedes Mitglied zu seinem " ungültiger Status standardmäßig Was Eigenschaften gegenüber Mitgliedern betrifft, ist das reale Objekt, mit dem ich arbeite, in einer externen Baugruppe definiert, die ich aufgrund von Abhängigkeitsproblemen nicht ändern kann. – Dan

1

Update: Da Sie nicht die Kontrolle über die Person Klasse und Staat in den öffentlichen Bereichen definiert, sondern als Eigenschaften, I haben die Lösung aktualisiert, um dies zu beheben.

Ich würde empfehlen, Reflexion zu verwenden. Sie möchten das Objekt FieldInfo (oder PropertyInfo) im Voraus erhalten, anstatt es für jeden Eintrag in Ihrer LINQ-Abfrage zu erhalten. Sie können sie erhalten, indem Sie Type.GetField und Type.GetProperty verwenden.Sobald Sie diese haben, können Sie einfach /PropertyInfo.GetValue und FieldInfo/PropertyInfo.SetValue verwenden.

Zum Beispiel:

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

Wie Sie wies darauf hin, sind Sie bereits die „ungültig“ vlaues im Standardkonstruktors Einstellung, so sollten Sie sich nicht über sie innerhalb dieser Schleife zu kümmern.

Hinweis: In meinem Beispiel habe ich die Versionen von GetField und GetProperties, die einen BindingFlags Parameter nicht nehmen Sie. Diese werden nur öffentliche Mitglieder zurückgeben.

+0

Es ist in der Tat nur eine Teilmenge der Eigenschaften. Soweit ungültige Werte, ist dies bereits im Konstruktor geschehen, wie ich gerade in meinem Kommentar zu digEmAll erwähnt habe. Ich mag Ihre Empfehlung, die PropertyInfo zu speichern, aber danke! – Dan

Verwandte Themen