Home > Software design >  Return a single object from a list of C# objects checking for matching properties
Return a single object from a list of C# objects checking for matching properties

Time:03-16

I have a requirement to create an object 'factory' that works as follows:

  1. Accepts a list of matching object types.
  2. Compares each property value in the list to see if they match.
  3. Returns a new single instance of that object type, with just the matching fields set.

(For simplicity, we can assume all propeties are strings)

For example, from this list of person objects:

Person1 { Name: "Bob", Job: "Policeman", Location: "London" }
Person2 { Name: "John", Job: "Dentist", Location: "Florida" }
Person3 { Name: "Mike", Job: "Dentist", Location: "London"  }
Person4 { Name: "Fred", Job: "Doctor", Location: "London"   }

If I passed in a list containing person 2 & 3 it would return a new person like so:

Name: "No Match", Job: "Dentist", Location "No Match"

If I passed in person 3 & 4 it would return a new person:

Name: "No Match", Job: "No Match", Location "London"

So far....
Using the answer from this SO question :
How to check if all list items have the same value and return it, or return an “otherValue” if they don’t?

I can get this LINQ to work for a single known object, but I need it to be generic.
This covers just one specific property but my objects have 30 properties.

var otherValue="No Match"
var matchingVal= people.First().Job;
return people.All(x=>x.Job== matchingVal) ? matchingVal: otherValue; 

I am also aware I can use reflection to get a list of properties in my object. But how to combine all of that into a single 'factory' is beyond by comprehension.

I don't think this is a unique problem but I cannot find a complete solution in any of my searching. Maybe there is already a Nuget package out there that can help me?

All advice gratefully received.

CodePudding user response:

Your input is an enumeration of some specific kind of objects and you have to create an object of the same type where all properties are filled, where all values are the same. This could be done with something like this:

private static T GetCommonProperties<T>(IEnumerable<T> source) where T : new()
{
    var first = true;
    var common = new T();
    var props = typeof(T).GetProperties();

    foreach (var item in source)
    {
        if (first)
        {
            first = false;

            foreach (var prop in props)
            {
                var value = prop.GetValue(item, null);
                prop.SetValue(common, value);
            }
        }
        else
        {
            foreach (var prop in props)
            {
                var itemValue = prop.GetValue(item, null);
                var commonValue = prop.GetValue(common, null);

                if ((dynamic)itemValue != (dynamic)commonValue)
                {
                    prop.SetValue(common, GetDefault(prop.PropertyType));
                }

            }
        }
    }

    return common;
}

The given method is not really optimal, cause it uses the dynamic trick to solve the comparison problem of boxed values. Also getting the default value for a specific type could probably implemented by this generic approach:

private static object GetDefault(Type t)
{
    return typeof(Program)
        .GetMethod(nameof(GetDefaultValue), BindingFlags.NonPublic | BindingFlags.Static)
        .MakeGenericMethod(t)
        .Invoke(null, null);
}

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

But it would also be possible to provide a switch statement or Dictionary<Type, object> that returns the desired default value if no match available.

Last but not least, another possible performance improvement would be to remove all PropertyInfo entries from the props variable, cause for any next upcoming object, this check is not necessary anymore and the same way, the loop could be early exited, when no more props are available anymore.

A working example can be found here.

  • Related