Skip to content

Commit

Permalink
Merge pull request #51 from phnx47/feat/binformat
Browse files Browse the repository at this point in the history
Replace BinaryFormatter with BinaryWriter
  • Loading branch information
phnx47 committed Apr 4, 2023
2 parents 111c3f5 + 33d2d01 commit dfa20d9
Show file tree
Hide file tree
Showing 30 changed files with 1,138 additions and 449 deletions.
5 changes: 2 additions & 3 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,14 @@ csharp_style_conditional_delegate_call = true:suggestion
csharp_new_line_before_else = true
csharp_new_line_before_catch = true
csharp_new_line_before_finally = true
csharp_new_line_before_members_in_object_initializers = true
csharp_new_line_before_members_in_anonymous_types = true

## Naming

### private fields should be _camelCase

dotnet_naming_style.underscore_prefix.capitalization = camel_case
dotnet_naming_style.underscore_prefix.required_prefix = _

# private fields should be _camelCase
dotnet_naming_rule.private_fields_with_underscore.symbols = private_fields
dotnet_naming_rule.private_fields_with_underscore.style = underscore_prefix
dotnet_naming_rule.private_fields_with_underscore.severity = suggestion
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
*.user
*.opencover.xml
*.orig

.idea

bin
obj
BenchmarkDotNet.Artifacts
32 changes: 16 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
Declare class:

```c#
class UserInfo
class User
{
public string FirstName { get; set; }
public string LastName { get; set; }
Expand All @@ -27,8 +27,8 @@ class UserInfo
Configure Func:

```c#
var fingerprint = FingerprintBuilder<UserInfo>
.Create(SHA256.Create().ComputeHash)
var sha256 = FingerprintBuilder<User>
.Create(SHA256.Create())
.For(p => p.FirstName)
.For(p => p.LastName)
.Build();
Expand All @@ -37,29 +37,29 @@ var fingerprint = FingerprintBuilder<UserInfo>
Get hash:

```c#
var user = new UserInfo { FirstName = "John", LastName = "Smith" };
var hash = fingerprint(user).ToLowerHexString(); // 9996c4bbc1da4938144886b27b7c680e75932b5a56d911754d75ae4e0a9b4f1a
var user = new User { FirstName = "John", LastName = "Smith" };
var hash = sha256(user).ToLowerHexString(); // 62565a67bf16004038c502eb68907411fcf7871c66ee01a1aa274cc18d9fb541
```

## Benchmarks

```ini
BenchmarkDotNet=v0.12.1, OS=arch

BenchmarkDotNet=v0.13.5, OS=arch
Intel Core i7-8565U CPU 1.80GHz (Whiskey Lake), 1 CPU, 8 logical and 4 physical cores
.NET Core SDK=3.1.300
[Host] : .NET Core 3.1.4 (CoreCLR 4.700.20.20201, CoreFX 4.700.20.22101), X64 RyuJIT
Job-PCQRMO : .NET Core 3.1.4 (CoreCLR 4.700.20.20201, CoreFX 4.700.20.22101), X64 RyuJIT
.NET SDK=7.0.103
[Host] : .NET 7.0.3 (7.0.323.12801), X64 RyuJIT AVX2
DefaultJob : .NET 7.0.3 (7.0.323.12801), X64 RyuJIT AVX2

Runtime=.NET Core 3.1 IterationCount=50 LaunchCount=2
RunStrategy=Throughput WarmupCount=10
```

| Method | Mean | Error | StdDev | Min | Max | Median |
|-------------------- |---------:|----------:|----------:|---------:|---------:|---------:|
| MD5_Model_To_Hex | 4.277 μs | 0.0763 μs | 0.2128 μs | 4.007 μs | 4.612 μs | 4.275 μs |
| SHA1_Model_To_Hex | 4.303 μs | 0.0232 μs | 0.0639 μs | 4.178 μs | 4.475 μs | 4.300 μs |
| SHA256_Model_To_Hex | 5.183 μs | 0.0526 μs | 0.1500 μs | 4.987 μs | 5.627 μs | 5.151 μs |
| SHA512_Model_To_Hex | 6.842 μs | 0.0688 μs | 0.1908 μs | 6.626 μs | 7.470 μs | 6.795 μs |
| MD5_Model_To_Hex | 2.142 μs | 0.0142 μs | 0.0118 μs | 2.125 μs | 2.163 μs | 2.146 μs |
| SHA1_Model_To_Hex | 2.379 μs | 0.0155 μs | 0.0121 μs | 2.355 μs | 2.400 μs | 2.384 μs |
| SHA256_Model_To_Hex | 3.059 μs | 0.0245 μs | 0.0217 μs | 3.031 μs | 3.107 μs | 3.054 μs |
| SHA512_Model_To_Hex | 4.564 μs | 0.0182 μs | 0.0161 μs | 4.540 μs | 4.598 μs | 4.563 μs |



## License

Expand Down
182 changes: 132 additions & 50 deletions src/FingerprintBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,76 +1,158 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Runtime.Serialization.Formatters.Binary;
using System.Security.Cryptography;

namespace FingerprintBuilder
namespace FingerprintBuilder;

public class FingerprintBuilder<T> : IFingerprintBuilder<T>
{
public class FingerprintBuilder<T> : IFingerprintBuilder<T>
private readonly Func<byte[], byte[]> _computeHash;
private readonly IDictionary<string, Func<T, object>> _fingerprints = new SortedDictionary<string, Func<T, object>>(StringComparer.OrdinalIgnoreCase);

private readonly Type[] _supportedTypes =
{
private readonly Func<byte[], byte[]> _computeHash;
private readonly IDictionary<string, Func<T, object>> _fingerprints;
typeof(bool),
typeof(byte),
typeof(sbyte),
typeof(byte[]),
typeof(char),
typeof(char[]),
typeof(double),
typeof(decimal),
typeof(short),
typeof(ushort),
typeof(int),
typeof(uint),
typeof(long),
typeof(ulong),
typeof(float),
typeof(string)
};

private FingerprintBuilder(Func<byte[], byte[]> computeHash)
{
_computeHash = computeHash ?? throw new ArgumentNullException(nameof(computeHash));
_fingerprints = new SortedDictionary<string, Func<T, object>>(StringComparer.OrdinalIgnoreCase);
}
private FingerprintBuilder(Func<byte[], byte[]> computeHash)
{
_computeHash = computeHash ?? throw new ArgumentNullException(nameof(computeHash));
}

public static IFingerprintBuilder<T> Create(Func<byte[], byte[]> computeHash) =>
new FingerprintBuilder<T>(computeHash);
public static IFingerprintBuilder<T> Create(HashAlgorithm hashAlgorithm) => Create(hashAlgorithm.ComputeHash);

public IFingerprintBuilder<T> For<TProperty>(Expression<Func<T, TProperty>> expression) =>
For<TProperty>(expression, _ => _);
public static IFingerprintBuilder<T> Create(Func<byte[], byte[]> computeHash) => new FingerprintBuilder<T>(computeHash);

public IFingerprintBuilder<T> For<TProperty>(Expression<Func<T, TProperty>> expression, Expression<Func<TProperty, string>> fingerprint) =>
For<TProperty, string>(expression, fingerprint);
public IFingerprintBuilder<T> For<TProperty>(Expression<Func<T, TProperty>> expression) => For(expression, f => f);

public IFingerprintBuilder<T> For<TProperty>(Expression<Func<T, TProperty>> expression, Expression<Func<TProperty, TProperty>> fingerprint)
public IFingerprintBuilder<T> For(Expression<Func<T, string>> expression, bool toLower, bool trim)
{
var format = (Func<string, string>)(input =>
{
return For<TProperty, TProperty>(expression, fingerprint);
}
if (toLower)
input = input.ToLowerInvariant();
private IFingerprintBuilder<T> For<TProperty, TPropertyType>(Expression<Func<T, TProperty>> expression, Expression<Func<TProperty, TPropertyType>> fingerprint)
{
if (!(expression.Body is MemberExpression memberExpression))
throw new ArgumentException("Expression must be a member expression");
if (trim)
input = input.Trim();
if (_fingerprints.ContainsKey(memberExpression.Member.Name))
throw new ArgumentException($"Member {memberExpression.Member.Name} has already been added.");
return input;
});

var getValue = expression.Compile();
var getFingerprint = fingerprint.Compile();
return For(expression, input => format(input));
}

_fingerprints[memberExpression.Member.Name] = obj =>
{
var value = getValue(obj);
return value == null ? default : getFingerprint(value);
};
public IFingerprintBuilder<T> For<TProperty>(Expression<Func<T, TProperty>> expression, Expression<Func<TProperty, string>> fingerprint) =>
For<TProperty, string>(expression, fingerprint);

return this;
}
private IFingerprintBuilder<T> For<TProperty, TReturnType>(Expression<Func<T, TProperty>> expression, Expression<Func<TProperty, TReturnType>> fingerprint)
{
if (expression.Body is not MemberExpression memberExpression)
throw new ArgumentException("Expression must be a member expression");

public Func<T, byte[]> Build()
var memberName = memberExpression.Member.Name;

if (_fingerprints.ContainsKey(memberExpression.Member.Name))
throw new ArgumentException("Member has already been added", memberName);

var returnType = typeof(TReturnType);
if (!_supportedTypes.Contains(typeof(TReturnType)))
throw new ArgumentException($"Unsupported Type: {returnType.Name}", memberName);

var getValue = expression.Compile();
var getFingerprint = fingerprint.Compile();

_fingerprints[memberExpression.Member.Name] = entity =>
{
var binaryFormatter = new BinaryFormatter();
var value = getValue(entity);
return value == null ? default : getFingerprint(value);
};

return obj =>
return this;
}

public Func<T, byte[]> Build()
{
return entity =>
{
using var memory = new MemoryStream();
using var binaryWriter = new BinaryWriter(memory);
foreach (var item in _fingerprints)
{
using (var memory = new MemoryStream())
var value = item.Value(entity);
switch (value)
{
foreach (var item in _fingerprints)
{
var graph = item.Value(obj);
if (graph != null)
binaryFormatter.Serialize(memory, graph);
}
var arr = memory.ToArray();
lock (_computeHash)
return _computeHash(arr);
case bool typedValue:
binaryWriter.Write(typedValue);
break;
case byte typedValue:
binaryWriter.Write(typedValue);
break;
case sbyte typedValue:
binaryWriter.Write(typedValue);
break;
case byte[] typedValue:
binaryWriter.Write(typedValue);
break;
case char typedValue:
binaryWriter.Write(typedValue);
break;
case char[] typedValue:
binaryWriter.Write(typedValue);
break;
case double typedValue:
binaryWriter.Write(typedValue);
break;
case decimal typedValue:
binaryWriter.Write(typedValue);
break;
case short typedValue:
binaryWriter.Write(typedValue);
break;
case ushort typedValue:
binaryWriter.Write(typedValue);
break;
case int typedValue:
binaryWriter.Write(typedValue);
break;
case uint typedValue:
binaryWriter.Write(typedValue);
break;
case long typedValue:
binaryWriter.Write(typedValue);
break;
case ulong typedValue:
binaryWriter.Write(typedValue);
break;
case float typedValue:
binaryWriter.Write(typedValue);
break;
case string typedValue:
binaryWriter.Write(typedValue);
break;
}
};
}
}
var bytes = memory.ToArray();
lock (_computeHash)
return _computeHash(bytes);
};
}
}
66 changes: 24 additions & 42 deletions src/FingerprintBuilderExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,53 +1,35 @@
using System;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;

namespace FingerprintBuilder
namespace FingerprintBuilder;

public static class FingerprintBuilderExtensions
{
public static class FingerprintBuilderExtensions
/// <summary>
/// Convert to LowerCase Hexadecimal string
/// </summary>
public static string ToLowerHexString(this byte[] source)
{
public static IFingerprintBuilder<T> For<T>(this IFingerprintBuilder<T> builder, Expression<Func<T, string>> expression, bool toLowerCase, bool ignoreWhiteSpace)
{
var format = (Func<string, string>)(input =>
{
if (toLowerCase)
input = input.ToLowerInvariant();
if (ignoreWhiteSpace)
input = input.Trim();
return input;
});

return builder.For(expression, input => format(input));
}

/// <summary>
/// Convert to LowerCase Hexadecimal string
/// </summary>
public static string ToLowerHexString(this byte[] source)
{
return source.ToString("x2");
}
return source.ToString("x2");
}

/// <summary>
/// Convert to UpperCase Hexadecimal string
/// </summary>
public static string ToUpperHexString(this byte[] source)
{
return source.ToString("X2");
}
/// <summary>
/// Convert to UpperCase Hexadecimal string
/// </summary>
public static string ToUpperHexString(this byte[] source)
{
return source.ToString("X2");
}

/// <summary>
/// Convert to string
/// </summary>
private static string ToString(this byte[] source, string format)
{
if (source == null)
throw new ArgumentNullException(nameof(source));
/// <summary>
/// Convert to string
/// </summary>
private static string ToString(this byte[] source, string format)
{
if (source == null)
throw new ArgumentNullException(nameof(source));

return string.Join("", source.Select(ch => ch.ToString(format, CultureInfo.InvariantCulture)));
}
return string.Join("", source.Select(ch => ch.ToString(format, CultureInfo.InvariantCulture)));
}
}

0 comments on commit dfa20d9

Please sign in to comment.