C
C#12mo ago
SWEETPONY

✅ How to find objects that changed?

I have following records:
public record NotificationSettingsDto
{
public string? NotificationSettingsId { get; set; }
public HashSet<string> EventCategories { get; set; }
public HashSet<string> EventNames { get; set; }
public HashSet<ChannelSettingsDto> ChannelSettings { get; set; }
public bool IsEnabled { get; set; }
public string TemplateMessage { get; set; }
}

public record ChannelSettingsDto
{
public string ChannelName { get; set; }
public BehaviorType Behavior { get; set; }
public TimeSpan Ttl { get; set; }
}

public enum BehaviorType
{
Simple,
RequiresConfirmation,
RequiresPersonalConfirmation
}
public record NotificationSettingsDto
{
public string? NotificationSettingsId { get; set; }
public HashSet<string> EventCategories { get; set; }
public HashSet<string> EventNames { get; set; }
public HashSet<ChannelSettingsDto> ChannelSettings { get; set; }
public bool IsEnabled { get; set; }
public string TemplateMessage { get; set; }
}

public record ChannelSettingsDto
{
public string ChannelName { get; set; }
public BehaviorType Behavior { get; set; }
public TimeSpan Ttl { get; set; }
}

public enum BehaviorType
{
Simple,
RequiresConfirmation,
RequiresPersonalConfirmation
}
Than I create lists with objects and trying to find objects that was changed. I don't know why but changedItems.Count is 1
var firstLst = new List<NotificationSettingsDto>()
{
new NotificationSettingsDto
{
NotificationSettingsId = "2",
IsEnabled = false,
EventCategories = new HashSet<string>(){"ac", "bc"},
EventNames = new HashSet<string>(){"cc", "dd"},
ChannelSettings = new HashSet<ChannelSettingsDto>()
{
new ChannelSettingsDto(){ ChannelName = "operator_notifications" }
}
}
};

var secondLst = new List<NotificationSettingsDto>()
{
new NotificationSettingsDto
{
NotificationSettingsId = "2",
IsEnabled = false,
EventCategories = new HashSet<string>(){"ac", "bc"},
EventNames = new HashSet<string>(){"cc", "dd"},
ChannelSettings = new HashSet<ChannelSettingsDto>()
{
new ChannelSettingsDto(){ChannelName = "operator_notifications"}
}
}
};

var changedItems = firstLst
.Join(secondLst,
f => f.NotificationSettingsId,
s => s.NotificationSettingsId,
(f, s) => new {First = f, Second = s})
.Where(pair => !pair.First.Equals(pair.Second))
.ToList();
var firstLst = new List<NotificationSettingsDto>()
{
new NotificationSettingsDto
{
NotificationSettingsId = "2",
IsEnabled = false,
EventCategories = new HashSet<string>(){"ac", "bc"},
EventNames = new HashSet<string>(){"cc", "dd"},
ChannelSettings = new HashSet<ChannelSettingsDto>()
{
new ChannelSettingsDto(){ ChannelName = "operator_notifications" }
}
}
};

var secondLst = new List<NotificationSettingsDto>()
{
new NotificationSettingsDto
{
NotificationSettingsId = "2",
IsEnabled = false,
EventCategories = new HashSet<string>(){"ac", "bc"},
EventNames = new HashSet<string>(){"cc", "dd"},
ChannelSettings = new HashSet<ChannelSettingsDto>()
{
new ChannelSettingsDto(){ChannelName = "operator_notifications"}
}
}
};

var changedItems = firstLst
.Join(secondLst,
f => f.NotificationSettingsId,
s => s.NotificationSettingsId,
(f, s) => new {First = f, Second = s})
.Where(pair => !pair.First.Equals(pair.Second))
.ToList();
20 Replies
SWEETPONY
SWEETPONYOP12mo ago
but actually objects are the same
exokem
exokem12mo ago
What do you mean by objects that changed The Join call produces a list with one element, I think because it's doing the equivalent of an inner join What is your goal with these two lists? Their contents seem to be identical except the objects will not point to the same reference
cap5lut
cap5lut12mo ago
the HashSet<T>s use the default Equals() check, which is a reference equality check
MODiX
MODiX12mo ago
cap5lut
REPL Result: Success
record R(HashSet<string> Names);
Console.WriteLine(new R(new()).Equals(new R(new())));
record R(HashSet<string> Names);
Console.WriteLine(new R(new()).Equals(new R(new())));
Console Output
False
False
Compile: 580.747ms | Execution: 107.539ms | React with ❌ to remove this embed.
cap5lut
cap5lut12mo ago
long story short, u would have to provide ur own implementation of bool Equals(object? other) for NotificationSettingsDto. which means u have to rewrite it as normal class as u can not override that method (note that u have to override int GetHashCode() as well then) oh, and this is also the case for most other reference types (especially collections)
SWEETPONY
SWEETPONYOP12mo ago
oh no catshy Ok, I will try to write my own implementation of GetHashCode and Equals
cap5lut
cap5lut12mo ago
dotnet provides a helper struct for the hash code: https://learn.microsoft.com/en-us/dotnet/api/system.hashcode?view=net-8.0 but it will still be tricky because of the hash set. even with the same contents, they could have a different order which u will have to account for
SWEETPONY
SWEETPONYOP12mo ago
public virtual bool Equals( NotificationSettingsDto? other )
{
return other != null
&& NotificationSettingsId.Equals(other.NotificationSettingsId)
&& EventCategories.SequenceEqual(other.EventCategories)
&& EventNames.SequenceEqual(other.EventNames)
&& ChannelSettings.SequenceEqual(other.ChannelSettings)
&& IsEnabled.Equals(other.IsEnabled)
&& TemplateMessage.Equals(other.TemplateMessage);
}
public virtual bool Equals( NotificationSettingsDto? other )
{
return other != null
&& NotificationSettingsId.Equals(other.NotificationSettingsId)
&& EventCategories.SequenceEqual(other.EventCategories)
&& EventNames.SequenceEqual(other.EventNames)
&& ChannelSettings.SequenceEqual(other.ChannelSettings)
&& IsEnabled.Equals(other.IsEnabled)
&& TemplateMessage.Equals(other.TemplateMessage);
}
is it correct? hmm doesn't work as expected for me haha
cap5lut
cap5lut12mo ago
if the underlying arrays of the hash sets have a different size, it can affect the buckets and thus the order in which the items are stored. so eg if u have an hashset A and B and they both contain only "hello" and "world", it could be that on iteration one would yield "hello" first, while the other yields "world" first thus SequenceEqual wont be enough either
SWEETPONY
SWEETPONYOP12mo ago
ok I will use List instead harold
cap5lut
cap5lut12mo ago
u would have to do something like
HashSet<string> temp = new(EventNames);
temp.UnionWith(other.EventNames);
temp.Count == EventNames.Count
HashSet<string> temp = new(EventNames);
temp.UnionWith(other.EventNames);
temp.Count == EventNames.Count
to test if they contain the same elements for the hash code u would have to sort the elements somehow and use that to compute the hash code
SWEETPONY
SWEETPONYOP12mo ago
I don't think it is optimal solution catshy Imagine doing this in each Equals
cap5lut
cap5lut12mo ago
or u use a SortedSet<T>, then u can use at least SequenceEqual and alike
SWEETPONY
SWEETPONYOP12mo ago
now it works:
public bool Equals( NotificationSettingsDto? other )
{
return other != null
&& NotificationSettingsId.Equals(other.NotificationSettingsId)
&& EventCategories.SequenceEqual(other.EventCategories)
&& EventNames.SequenceEqual(other.EventNames)
&& ChannelSettings.SequenceEqual(other.ChannelSettings)
&& IsEnabled.Equals(other.IsEnabled)
&& TemplateMessage.Equals(other.TemplateMessage);
}

public override int GetHashCode()
{
return HashCode.Combine(
NotificationSettingsId,
EventCategories,
EventNames,
ChannelSettings,
IsEnabled,
TemplateMessage);
}
public bool Equals( NotificationSettingsDto? other )
{
return other != null
&& NotificationSettingsId.Equals(other.NotificationSettingsId)
&& EventCategories.SequenceEqual(other.EventCategories)
&& EventNames.SequenceEqual(other.EventNames)
&& ChannelSettings.SequenceEqual(other.ChannelSettings)
&& IsEnabled.Equals(other.IsEnabled)
&& TemplateMessage.Equals(other.TemplateMessage);
}

public override int GetHashCode()
{
return HashCode.Combine(
NotificationSettingsId,
EventCategories,
EventNames,
ChannelSettings,
IsEnabled,
TemplateMessage);
}
thanks for helping
cap5lut
cap5lut12mo ago
im pretty sure the hashcode is still wrong
MODiX
MODiX12mo ago
cap5lut
REPL Result: Success
Console.WriteLine(new SortedSet<string>().GetHashCode());
Console.WriteLine(new SortedSet<string>().GetHashCode());
Console.WriteLine(new SortedSet<string>().GetHashCode());
Console.WriteLine(new SortedSet<string>().GetHashCode());
Console Output
37582766
45729591
37582766
45729591
Compile: 458.462ms | Execution: 31.802ms | React with ❌ to remove this embed.
cap5lut
cap5lut12mo ago
u would have to do something like
HashCode hashCode = new();
hashCode.Add(NotificationSettingsId);
foreach (var category in EventCategories) hashCode.Add(category);
// ...
return hashCode.ToHashCode();
HashCode hashCode = new();
hashCode.Add(NotificationSettingsId);
foreach (var category in EventCategories) hashCode.Add(category);
// ...
return hashCode.ToHashCode();
SWEETPONY
SWEETPONYOP12mo ago
public override int GetHashCode()
{
var hash = new HashCode();

hash.Add(NotificationSettingsId);
hash.Add(IsEnabled);
hash.Add(TemplateMessage);

foreach (var eventCategory in EventCategories)
hash.Add(eventCategory);

foreach (var eventName in EventNames)
hash.Add(eventName);

foreach (var channelSettings in ChannelSettings)
hash.Add(channelSettings);

return hash.ToHashCode();
}
public override int GetHashCode()
{
var hash = new HashCode();

hash.Add(NotificationSettingsId);
hash.Add(IsEnabled);
hash.Add(TemplateMessage);

foreach (var eventCategory in EventCategories)
hash.Add(eventCategory);

foreach (var eventName in EventNames)
hash.Add(eventName);

foreach (var channelSettings in ChannelSettings)
hash.Add(channelSettings);

return hash.ToHashCode();
}
done! I hope it's optimal way to get hashcode
cap5lut
cap5lut12mo ago
its definitifely the recommended way ;p but now that i think about it, it might also be okay to just use the NotificationSettingsId for the hash code the contract is basically, if 2 objects of the same type yield true for Equals(), they have to have the same hash code, but not vice versa, meaning two objects producing the same hash code, do not have to return true for Equals()
SWEETPONY
SWEETPONYOP12mo ago
thanks for helping bro Ok
Want results from more Discord servers?
Add your server