using System.Collections.Generic;
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
internal sealed class IgnoreMemberAttribute : Attribute
internal interface IValueObject<T>
public int GetHashCode();
public int HashValue(int seed, object value);
public abstract class ValueObject : IValueObject<ValueObject>
private List<FieldInfo>? _fields;
private List<PropertyInfo>? _properties;
#region IValueObject<ValueObject> Members
public override int GetHashCode()
var hash = GetProperties()
.Select(property => property.GetValue(this, null))
.Aggregate(17, HashValue);
.Select(field => field.GetValue(this))
.Aggregate(hash, HashValue);
public int HashValue(int seed, object? value)
var currentHash = value?.GetHashCode() ?? 0;
return seed * 23 + currentHash;
public virtual bool Equals(ValueObject? other) => Equals(other as object);
public static bool operator ==(ValueObject? left, ValueObject? right)
if (left is null ^ right is null) return false;
return left?.Equals(right) != false;
public static bool operator !=(ValueObject? left, ValueObject? right) => !(left == right);
public override bool Equals(object? obj)
|| obj.GetType() != GetType()) return false;
return GetProperties().All(p => PropertiesAreEqual(obj, p))
&& GetFields().All(f => FieldsAreEqual(obj, f));
private bool PropertiesAreEqual(object obj, PropertyInfo p) =>
Equals(p.GetValue(this, null), p.GetValue(obj, null));
private bool FieldsAreEqual(object obj, FieldInfo f) => Equals(f.GetValue(this), f.GetValue(obj));
private IEnumerable<PropertyInfo> GetProperties()
return _properties ??= GetType()
.GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Where(p => p.GetCustomAttribute(typeof(IgnoreMemberAttribute)) is null)
private IEnumerable<FieldInfo> GetFields()
return _fields ??= GetType()
.GetFields(BindingFlags.Instance | BindingFlags.Public)
.Where(f => f.GetCustomAttribute(typeof(IgnoreMemberAttribute)) is null)
public sealed class FullNameVo : ValueObject
public FullNameVo(string name, string surname)
public string Name { get; }
public string Surname { get; }
public string FullName => $"{Name} {Surname}";
private static void Main(string[] args)
var user1 = new FullNameVo("John", "Doe");
var user2 = new FullNameVo("John", "Doe");
var user3 = new FullNameVo("Jane", "Doe");
Console.WriteLine(user1 == user2);
Console.WriteLine(ReferenceEquals(user1, user2));
Console.WriteLine(user1 == user3);
Console.WriteLine(user1.Equals(user3));