0

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.

4
  • What property types should be supported (e.g. string, int, DateTime, classes). In your example what is with property ID? How does the method know which properties should be compared? Commented Mar 16, 2022 at 9:30
  • @Oliver The ID was just there to reference in my example. I have changed it to make it clearer. All properties should be compared. Commented Mar 16, 2022 at 9:41
  • Where is for each type defined, what should be inserted if no match was found (e.g. int = -1, string = null or string = "no match", DateTime = DateTime.MaxValue, etc.) Commented Mar 16, 2022 at 9:41
  • We can treat everything as a string. So if the strings match the new object will have that value. If they don't, the new object gets some arbitrary value in that property, like: "No Match" Commented Mar 16, 2022 at 9:51

1 Answer 1

1

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.

Sign up to request clarification or add additional context in comments.

3 Comments

Wow! @Oliver this is exactly what I was after. Thank you so much for taking the time and giving such a comprehensive solution.
Great to hear. Nevertheless you should probably implement the mentioned improvements, especially if you call this thing very often, in loops or with plenty of elements in the source.
Duly noted. Thanks again.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.