How to compare properties between two objects - c#

I have two similar classes : Person , PersonDto
public class Person
{
public string Name { get; set; }
public long Serial { get; set; }
public DateTime Date1 { get; set; }
public DateTime? Date2 { get; set; }
}
&
public class PersonDto
{
public string Name { get; set; }
public long Serial { get; set; }
public DateTime Date1 { get; set; }
public DateTime? Date2 { get; set; }
}
I have two objects of both by equal values.
var person = new Person { Name = null , Serial = 123, Date1 = DateTime.Now.Date, Date2 = DateTime.Now.Date };
var dto = new PersonDto { Name = "AAA", Serial = 123, Date1 = DateTime.Now.Date, Date2 = DateTime.Now.Date };
I need to check value of all properties in two classes by reflection. My final goal is defined difference value of this properties.
IList diffProperties = new ArrayList();
foreach (var item in person.GetType().GetProperties())
{
if (item.GetValue(person, null) != dto.GetType().GetProperty(item.Name).GetValue(dto, null))
diffProperties.Add(item);
}
I did this, but result is not satisfactory. Count of diffProperties for result was 4but count of my expect was 1.
Of course all properties can have null values.
I need to a solution generic.
What must do I?

If you want to stick with comparison via reflection you should not use != (reference equality which will fail most of comparisons for boxed results of GetProperty calls) but instead use static Object.Equals method.
Sample how to use Equals method to compare two object in your reflection code.
if (!Object.Equals(
item.GetValue(person, null),
dto.GetType().GetProperty(item.Name).GetValue(dto, null)))
{
diffProperties.Add(item);
}

You may consider making the Person class implement the IComparable interface and implementing the CompareTo(Object obj) method.

Implement IEquatable interface

Looking at you classes not all values can be null you have a nullable long.
But that said.
I also made something like this and used this website for it. Just make it so that it can accept 2 different objects. I can't share my code because of licensing sorry.

Related

Is there a way to type check a generic function parameter using the shape of the object instead of inheritance?

I have a few EF model classes that I want to create. Each class has a few common properties that I want to set before inserting a new entity, for example:
public partial class BlogPost {
public DateTime CreatedTime { get; set; }
public string CreatorName { get; set; }
public string PostTitle { get; set; }
public string PostText { get; set; }
}
public partial class Comment {
public DateTime CreatedTime { get; set; }
public string CreatorName { get; set; }
public string CommentText { get; set; }
}
...
When I create these classes, I'm instantiating them like so:
var blogPost = new BlogPost {
CreatedTime = DateTime.UtcNow,
CreatorName = creatorName,
PostTitle = postTitle,
PostText = postText,
};
var comment = new Comment {
CreatedTime = DateTime.UtcNow,
CreatorName = creatorName,
...
};
...
I want to create a method to automatically set some of the common properties so I don't need to manually type them out for each class with the same properties. Since they don't extend the same class or implement the same interface, I'm wondering how this can be achieved. My first thought was to use a generic method; however, I don't know if there's a way to specify what properties the generic type should have without them extending the same class (similar to TypeScript's "duck typing"). My desired method looks something like this:
public void SetInitialProperties<T>(T dbEntity, DateTime createdTime, string creatorName) where T : ??? {
dbEntity.CreatedTime = createdTime;
dbEntity.CreatorName = creatorName;
}
...
var blogPost = new BlogPost { PostTitle = postTitle, PostText = postText };
SetInitialProperties(blogPost, createdTime, creatorName);
Worst case scenario if I can't use a generic, I could always use dynamic; however, I'd like to keep type checking if possible.
You can achieve what you want using reflection. You can pass in an object and resolve it's type, then get all the public properties of that given type and find if you have one called CreatedTime for example. Then you can set the value of the given property on the passed dbEntity object. However, I do not recommend this solution:
public void SetInitialProperties(object dbEntity, DateTime createdTime, string creatorName) {
// get the passed object's properties and find the one called CreatedTime
var createdTimePropertyInfo = dbEntity.GetType().GetProperties().Where(i => i.Name == "CreatedTime").FirstOrDefault();
// below line is equal to: dbEntity.CreatedTime = createdTime;
createdTimePropertyInfo.SetValue(dbEntity, createdTime);
var creatorNamePropertyInfo = dbEntity.GetType().GetProperties().Where(i => i.Name == "CreatorName").FirstOrDefault();
creatorNamePropertyInfo.SetValue(dbEntity, creatorName);
}
You would be better off on the long run by creating a common interface or even an abstract base class so you don't have to implement CreatedTime and CreatorName and other properties for every EF model. It would look like the following:
public interface IUserEntry
{
DateTime CreatedTime { get; set; }
string CreatorName { get; set; }
}
public abstract class UserEntryBase : IUserEntry
{
public DateTime CreatedTime { get; set; }
public string CreatorName { get; set; }
}
public partial class BlogPost : UserEntryBase
{
public string PostTitle { get; set; }
public string PostText { get; set; }
}
public partial class Comment : UserEntryBase
{
public string CommentText { get; set; }
}
And your SetInitialProperties would be pretty simple:
public void SetInitialProperties(IUserEntry dbEntity, DateTime createdTime, string creatorName)
{
dbEntity.CreatedTime = createdTime;
dbEntity.CreatorName = creatorName;
}
Once you develop onto an interface, you achieve much more flexibility than by using reflection or a dynamic type, since you get the compile-time checking that was mentioned before me and you can see the common properties of your models.
You can't do that in C# because C# uses a nominal type system and not a structural type system.
For your particular case you have to come up with an interface that contains the properties in common and which will be implemented by both entities, then use that new interface as you generic function parameter constraint.
If you're absolutely sure the properties will have the same name, you could pass a dynamic to set property values. However, this prevents any compile-time checking of the typing, so if you accidently pass an incompatible type it won't be caught until runtime.
public void SetInitialProperties(dynamic dbEntity, DateTime createdTime, string creatorName) {
dbEntity.CreatedTime = createdTime;
dbEntity.CreatorName = creatorName;
}

DateTime as class generic constraint

Is there a way in C# to add a class constraint that simply makes sure that the type is either DateTime or Nullable<DateTime>? Or makes sure that they have an == operator that is compatible with DateTime?
Below is a simplified use case where I have a method which I try to re-use between two entitites. In reality it is reused between four different entitites. The all share a "similiar" interface but some have a DateTime? and others have DateTime for their Created-property.
I really just need to make sure that they can compare with a specific DateTime which is fetched by another method GetDateForLastMonth().
Limitations for this is that I cannot change the properties for the entities. Although, I can add new interfaces for them. I cannot change the returned type from GetDateForLastMonth.
public class Context : DbContext
{
public virtual DbSet<OrderEntity> Orders { get; set; }
public virtual DbSet<InvoiceEntity> Invoices { get; set; }
public IEnumerable<T> GetItemsForSomePeriod<T, TDateType>(DbSet<T> dbSet)
where T : class, IHaveACreationDate<TDateType>
where TDateType : DateTime // Does not compile because "Only class or interface could be specified as constraint"
{
DateTime someDate = GetSomeDate();
DateTime someOtherDate = GetSomeOtherDate();
DateTime someThirdDate = GetAThirdDate();
return dbSet
.Where(x => (x.Created == lastMonth || x.Created == someOtherDate || x.Create == someThirdDate)) // Cannot compare because Created and someDate isn't same Type
.ToArray();
}
}
public interface IHaveACreationDate<T>
{
T Created { get;}
}
public class OrderEntity: IHaveACreationDate<DateTime?>
{
public DateTime? Created { get; set; }
}
public class InvoiceEntity : IHaveACreationDate<DateTime>
{
public DateTime Created { get; set; }
}

Assignment same property values to other

I have different classes sharing some properties of same type and name. I wish to assign same property values to each other. I explain my intention better in comments in the following pseudo-code. Is it possible in C#?
Ponder that there are a plethora of common properties but in unrelated classes, must we assign them one-by-one?
Second case is about sharing same properties but some of them may be nullable, who knows!
Side note: the classes already exist, cannot be altered, touched. Kinda sealed.
Can't it be done using nameofoperator and two for loops? Compare property names if matched, assign?
using System;
namespace MainProgram
{
class HomeFood
{
public DateTime Date { get; set; }
public string food1 { get; set; }
public string food2 { get; set; }
public int cucumberSize { get; set; }
}
class AuntFood
{
public string food2 { get; set; }
public int cucumberSize { get; set; }
public DateTime Date { get; set; }
public string food1 { get; set; }
// extra
public double? length { get; set; }
}
class GrandpaFood
{
public string? food2 { get; set; }
public int cucumberSize { get; set; }
public DateTime? Date { get; set; }
public string food1 { get; set; }
// extra
}
static class Program
{
public static void Main(string[] args)
{
var home = new HomeFood
{
Date = new DateTime(2020, 1, 1),
food1 = "cucumber",
food2 = "tomato",
cucumberSize = 123
};
var aunt = new AuntFood();
/*
First case: same types
Expected for-each loop
assigning a class's property values
to other class's property values
or for-loop no matter
foreach(var property in HomeFood's properties)
assign property's value to AuntFood's same property
*/
var home2 = new HomeFood();
var grandpa = new GrandpaFood
{
Date = new DateTime(2020, 1, 1),
food1 = "dfgf",
food2 = "dfgdgfdg",
cucumberSize = 43534
};
/*
Second case: similar to first case
with the exception of same type but nullable
or for-loop no matter
foreach(var property in GrandpaFood's properties)
assign property's value to GrandpaFood's same property
we don't care if it is null e.g.
Home2's same property = property's value ?? default;
*/
}
}
}
Based on the comments in the questions, this is just to show how it can be done with reflection.
Disclaimer, this is just a very simplified example on how to use reflection to sync properties. It does not handle any special cases (modifiers, read only, type mismatch, etc)
I would strongly suggest to use automapper to achieve the qp goals.
public class Type1
{
public string Property1 { get; set; }
public string Property2 { get; set; }
}
public class Type2
{
public string Property1 { get; set; }
public string Property3 { get; set; }
}
class Program
{
static void Main(string[] args)
{
var t1 = new Type1 { Property1 = "Banana" };
var t2 = new Type2();
var properties1 = typeof(Type1).GetProperties().ToList();
var properties2 = typeof(Type2).GetProperties().ToList();
foreach(var p in properties1)
{
var found = properties2.FirstOrDefault(i => i.Name == p.Name);
if(found != null)
{
found.SetValue(t2, p.GetValue(t1));
}
}
Console.WriteLine(t2.Property1);
}
}
The short answer is, apply OOP. Define a base Food class and inherit from it in any specific food classes you have. You can put all the shared props in the base class.
public class Food
{
public string food2 { get; set; }
// other shared stuff
}
class GrandpaFood : Food
{
// other specific stuff
}
As others have said, use some of the Object Oriented properties, like inheriting a super class of implement an interface.
In case you go for inheritance, consider making the super class (the one you inherit from) abstract. This means that the super class itself cannot be instantiated, which greatly reduces the risk of violating the Liskov Substitutional Principle. Also it often reflects the real problem better. In your example, this would also be the case, as “food” is not an actual thing in the real world, but rather a group of things.

Find in List<object> where each item is in an array

I'm making a web service call and getting data back that I am adding to a list. No problems doing that. My remedyinfo list has content that I can verify with break points in VS.
I cannot seem to figure out how to search the list for a value that matches a new variable, for instance I want to find if incidentID is equal to "INC000000001".
I've tried var foundItem = remedyinfo.Contains("searchvalue") but it always returns false.
I've tried the LINQ queries as suggested in other post:
var foundItem = myArray.SingleOrDefault(item => item.intProperty == someValue);
What I do notice is that the sample refers to comparing item.intProperty == somevalue) should work, I am not able to get any reference after item., only suggested Equals, GetType, GetHashCode and ToString. So, I cannot reference item.incidentID for example.
Any guidance is greatly appreciated.
var remedyinfo = new List<object> { };
remedyinfo.Add(new IncidentItem()
{
assignedgroup = assignedgroup,
incidentID = incidentID,
submitdate = offsetDate.ToString(),
priority = priority,
status = status,
assignee = assignee,
summarydesc = summarydesc,
notes = notes
});
[Serializable]
public class IncidentItem
{
public string assignedgroup { get; set; }
public string incidentID { get; set; }
public string submitdate { get; set; }
public string priority { get; set; }
public string status { get; set; }
public string assignee { get; set; }
public string summarydesc { get; set; }
public string notes { get; set; }
}
To check if list contains an item, there are different options. You can use Any in .Net 3.5 or higher:
if (remedyinfo.Any(incident => incident.incidentID == "Hello"))
// rest of code
If you have another IncidentItem and wish to check in list like
var anotherIncident = new IncidentItem();
if (remedyInfo.Contains(anotherIncident))
then you either have to implement IEquatable or override Equals and HashCode in IncidentItem class.
More on Equals and HashCode is here:
Correct way to override Equals() and GetHashCode()

Dynamic equality checking of multiple properties of a type's objects

I have a type like:
class Order
{
public List<IItem> AllItems { get; set; }
public string Name { get; set; }
public double TotalPurchases { get; set; }
public long Amount { get; set; }
public int Code { get; set; }
}
I've implemented the IEquatable<T> interface to check if two objects of this type are same or not. The current Equals method looks like:
public virtual bool Equals(Order other)
{
if ((object)other == null)
{
return false;
}
return (this.AllItems.Equals(other.AllItems)
&& this.Name.Equals(other.Name)
&& this.TotalPurchases.Equals(other.TotalPurchases)
&& this.Amount.Equals(other.Amount))
&& this.Code.Equals(other.Code));
}
But I wish to implement this method in such a way that it dynamically checks for equality of all the existing properties (or maybe certain properties of this type) without explicitly writing the code for comparison checks as above.
Hope I was able to express my question with clarity. :)
Thanks!
You could write a custom attribute that attaches to the properties on your type which you want to be included in the comparision. Then in the Equals method you could reflect over the type and extract all the properties which have the attribute, and run a comparison on them dynamically.
Psuedo code:
[AttributeUsage(AttributeTarget.Property)]
class IncludeInComparisonAttribute : Attribute { }
class Order
{
List<AllItem> Items { get; set; }
[IncludeInComparison]
string Name { get; set; }
long Amount { get; set; }
[IncludeInComparison]
int Code { get; set; }
override bool Equals(Order other)
{
Type orderType = typeof(Order);
foreach (PropertyInfo property in orderType.GetProperties()
{
if (property.CustomAttributes.Includes(typeof(IncludeInComparisonAttribute))
{
object value1 = property.GetValue(this);
object value2 = propetty.GetValue(other);
if (value1.Equals(value2) == false)
return false;
}
}
return true;
}
}
It'll certianly need to be a bit more elaborate than that, but that should hopefully set you on the right track :)
Two Orders are considered the same if all their properties are equal. It's OK for the 4 properties Name/TotalPurchases/Amount/Code, their default comparers are exactly what you want. But for the property AllItems (whose type is List<IItem>), you must tell how they consider to be equal. Currently you are using reference equals that is incorrect. this.AllItems.Equals(other.AllItems) should be something like:
this.AllItems.SequenceEqual(other.AllItems, new ItemComparer())
And the ItemComparer is a class implements IEqualityComparer<Item> to tell how to check if two Items are equal.

Categories

Resources