Я оказался загнанным в угол, так что поехали.
Контекст
Мне нужно создать хэш-код отпечатка пальца для сравнения объектов. Сравнение хэшей двух наборов объектов должно будет сказать мне, есть ли идентичные объекты с одинаковым хешем.
Отпечаток хэш должен быть независимым от платформы. Поэтому я выбрал хеширование MD5.
Я работаю с большой базой кода объектной модели, которая находится вне моего контроля. Все типы, которые будут переданы мне для этого снятия отпечатков пальцев, не могут быть изменены мной. Я не могу добавить атрибут или конструкторы или что-либо изменить. Что не исключает, что в будущем типы изменятся. Так что любой подход должен быть программным — я не могу просто создать суррогатный класс, чтобы избежать проблемы; по крайней мере, не вручную.
Однако производительность не имеет значения, поэтому отражение полностью разрешено.
Кроме того, мне нужно будет иметь возможность управлять исключением свойств из хеширования. Если я исключаю определенное свойство, два объекта, которые имеют все свойства, идентичные друг другу, за исключением того, что все равно нужно будет получить один и тот же хэш.
Проблема: сериализация в Byte[]
со связанными руками над устаревшим кодом
Хэширование MD5 требует, чтобы объект был сериализован в Byte[].
Сериализация требует, чтобы класс был помечен как [Serializable]
. Который я не могу добавить в устаревший код, и, естественно, его нельзя добавить и во время выполнения.
Поэтому я выбрал protobuf-net
.
public interface ISomeInterface
{
double Vpy { get; }
double Vy { get; }
double Vpz { get; }
...
}
Поскольку этот интерфейс реализован многими типами, использование Surrogates также кажется неприемлемым (непрактичным, необслуживаемым).
Мне просто нужно сериализовать, а не десериализовать, поэтому я не понимаю, почему в этом случае ограничение protobuf-net. Я понимаю, что protobuf-net не сможет выполнить обмен данными, если это необходимо, но мне это не нужно!
Вопрос
Я действительно загнан в угол? Есть ли альтернатива?
Мой код
Как я уже сказал, это прекрасно работает, но только в том случае, если объекты не имеют какого-либо свойства (или вложенного свойства), которое является типом с автоматическим свойством только для Getter.
public static byte[] ToByteArray(this object obj, List<PropertyInfo> exclusionsProps = null)
{
if (exclusionsProps == null)
exclusionsProps = new List<PropertyInfo>();
// Protobuf-net implementation
ProtoBuf.Meta.RuntimeTypeModel model = ProtoBuf.Meta.TypeModel.Create();
AddPropsToModel(model, obj.GetType(), exclusionsProps);
byte[] bytes;
using (var memoryStream = new MemoryStream())
{
model.Serialize(memoryStream, obj);
bytes = memoryStream.GetBuffer();
}
return bytes;
}
public static void AddPropsToModel(ProtoBuf.Meta.RuntimeTypeModel model, Type objType, List<PropertyInfo> exclusionsProps = null)
{
List<PropertyInfo> props = new List<PropertyInfo>();
if (exclusionsProps != null)
props.RemoveAll(pr => exclusionsProps.Exists(t => t.DeclaringType == pr.DeclaringType && t.Name == pr.Name));
props
.Where(prop => prop.PropertyType.IsClass || prop.PropertyType.IsInterface).ToList()
.ForEach(prop =>
{
AddPropsToModel(model, prop.PropertyType, exclusionsProps); //recursive call
}
);
var propsNames = props.Select(p => p.Name).OrderBy(name => name).ToList();
model.Add(objType, true).Add(propsNames.ToArray());
}
Который я затем буду использовать как таковой:
foreach (var obj in objs)
{
byte[] objByte = obj.ToByteArray(exclusionTypes);
using (MD5 md5Hash = MD5.Create())
{
string hash = GetMd5Hash(md5Hash, objByte);
Console.WriteLine(obj.GetType().Name + ": " + hash);
}
}