Good morning, my problem is the following. Assuming I have a class Persona
, and I create two objects called Persona1
and persona2
. I need a method that can return a new object Persona
with the differences in the variables that they have between them: Class:
public class Persona
{
public int Edad {get;set;}
public int Nombre {get;set;}
}
Instances:
Persona Persona1=new Persona{ Edad=18, Nombre="Luis"};
Persona Persona2=new Persona{ Edad=18, Nombre="Carlos"};
In principle, it occurs to me to make a method that compares variable by variable to know the differences, something like:
public Persona SonIguales(Persona a, Persona b){
Persona diferencias=new Persona();
if(a.Edad==b.Edad){
diferencias.Edad=a.Edad;
}if(a.Nombre.equals(b.Nombre)){
diferencias.Nombre=a.Nombre;
}
return diferencias;
}
which would work perfectly, but in the real object that I have to apply it to, it has many variables, which would make it quite tedious to do this one by one. Is there a way to do something like this without having to check field by field?
For these kind of solutions Reflection can be used .
We start by defining a generic and static function that admits two instances of the same type.
We add the constraint
TModel : new()
because we will need to instantiate a new object of the classTModel
with no arguments.We get all the properties of the type
TModel
.The method
GetProperties
has an overload that supportsBindingFlags
. If needed, it could be modified to access different properties . By default, properties that are static (and public)are accessed . Since we are not interested in static properties in our case (by definition, two instances of the same class will always have the same value in a static property ), we will filter those properties .
Finally, we are going to create a new instance of
TModel
and, looping through all the obtained properties , we " set " those properties that are not the same in the two original objects (with the value of the first instance, as in your example).Where the function
AreEqual
does a nullcheck and queries the standard methodEqual
of theobject
.The solution is trivial in case of simple types* (
int
,string
,bool
, ...).For more complex cases, I recommend you to implement the method
Equals
of those specific types or, in the case of already defined complex types, add conditions within theAggregate
or of the functionAreEqual
to determine if they are equal or not, depending on your needs.* I don't mean primitive types because
string
it isn't.Lastly, I'm posting a link to the ready-to-run .NET Fiddle , in case you want to do any kind of testing, and I'm also posting the full implementation below ( C# 8.0 required ).
Edit 1
I have explored the possibility of generalizing the solution to be more suitable for working with complex types.
It is not entirely simple; As I commented in the disclaimer , working with reflection always complicates things a bit, but valid solutions are reached.
The goal is for a property of a complex type to be parsed beyond the comparison
Equals
that, if it is not overridden, it will only evaluate whether it is the same instance and not the equivalence of its properties .To do this, the function
AreEqual
is going to have to callDifference
(we apply recursion ) to check the differences between its properties .We must differentiate those properties that are of primitive types and those that are of complex types.
We separate the case from
string
because it is a complex type, but we don't want to deal with it with ourAreEqualClass
.The function
AreEqualClass
will take the two values that we now know to be of complex type, evaluate their differences and check that all the properties of the result are default (remember that itDifference
creates a new instance whose properties have the value of one of the two objects in case the property is different in both).At this point a problem arises that was not solved before: as I implemented the function , it always put the value of the first object
Difference
as the value of a different property . Conceptually, this is wrong as this value can benull
when the other object's is not, thus giving a false positive. We must assign the value of that object whose property is notnull
.To do this, we make use of the implemented function
IsDefault<>
.This way we make sure that we never add a
null
explicitly.On the other hand, due to reflection 's own rules , once we obtain the values of the properties we will be working with
object
. That is, the type argument of our generic methods will beobject
, too.This is why we must retrieve the types with
value.GetType()
.We have to refactor the function
AreEqualClass
to not work withobject
and also the functionDifference
that, when called from the recursion,TModel
will also beobject
.For the same reason that the type argument is
object
, the instruction is no longer validnew TModel()
, which would be equivalent tonew object()
so we could not assign properties of the type we are looking for.Therefore, we must build our result by reflection , once again.
And, with that change, the compiler forces us to add the constraint
class
to the function signatureDifference
and, therefore, inAreEqualClass
. We keep the constraintnew()
so that we can actually call a parameterless constructor.We want the compiler to disallow calling
Difference
if the type argument is a model that doesn't implement a parameterless constructor.Also, in the function
Difference
we add an extra nullcheck when obtaining the values to avoid problems (since all the nullchecks go to later, in theAreEqual
).I add a new fiddle with the new changes explained.
Bringing this code into production is very likely to be dangerous and buggy. That is why I recommend that an exhaustive test be carried out with real cases where it is going to be applied, but it can give a starting point to extend the functionality with each specific case.
I hope it works.
Edit 2
As of C# 9, you can declare
record
, which have all of this functionality built in. At the end of the day, they are just classes (orstruct
, if you declare it that way; as of C# 10) that will implement compare methods at compile time.If you're just looking for equalization functionality, this is how to implement it. In case you still need the result instance with properties that differ between two instances, the answer would still be valid.
More info. about those
record
here .