Created
May 3, 2020 14:04
-
-
Save 0x1000000/668bb6286042bceba6ecb0f40c1d91d3 to your computer and use it in GitHub Desktop.
For https://habr.com/ru/post/499562 (FastReslectionForHabrahabr)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using BenchmarkDotNet.Running; | |
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Linq.Expressions; | |
using System.Reflection; | |
using BenchmarkDotNet.Attributes; | |
namespace FastReslectionForHabrahabr | |
{ | |
public class Program | |
{ | |
static void Main(string[] args) | |
{ | |
BenchmarkRunner.Run(typeof(Benchmarks)); | |
Console.ReadKey(); | |
} | |
} | |
[MemoryDiagnoser] | |
[Orderer(BenchmarkDotNet.Order.SummaryOrderPolicy.FastestToSlowest)] | |
public class Benchmarks | |
{ | |
[Params(1)] | |
public int N = 1; | |
public static string[] RawData = | |
{ | |
$"ФИО:Иванов Иван Иванович1{Environment.NewLine}Тел.:+78886543422{Environment.NewLine}Возраст:22", | |
$"Фамилия Имя Отчество:Иванов Иван Иванович2{Environment.NewLine}Тел.:+78886543422{Environment.NewLine}Возраст:22", | |
$"ФИО:Иванов Иван Иванович3{Environment.NewLine}Телефон:+78886543422{Environment.NewLine}Возраст:22" | |
}; | |
private PropertyToValueCorrelation[][] _data; | |
private FastContactHydrator _fastContactHydrator; | |
private SlowContactHydrator _slowContactHydrator; | |
[GlobalSetup] | |
public void SetUp() | |
{ | |
this._fastContactHydrator = new FastContactHydrator(); | |
this._slowContactHydrator = new SlowContactHydrator(); | |
var parser = new DefaultRawStringParser(); | |
this._data = RawData | |
.Select(r => ContactHydratorBase.GetPropertiesValuesWithoutLinq(parser, r)) | |
.ToArray(); | |
} | |
[Benchmark] | |
public void FastHydration() | |
{ | |
for (int i = 0; i < N; i++) | |
{ | |
foreach (var data in this._data) | |
{ | |
HydrateWithoutLinq(this._fastContactHydrator, data); | |
} | |
} | |
} | |
[Benchmark] | |
public void SlowHydration() | |
{ | |
for (int i = 0; i < N; i++) | |
{ | |
foreach (var data in this._data) | |
{ | |
HydrateWithoutLinq(this._slowContactHydrator, data); | |
} | |
} | |
} | |
private static void HydrateWithoutLinq(ContactHydratorBase parser, PropertyToValueCorrelation[] correlation) | |
{ | |
var contact = parser.GetContact(correlation); | |
} | |
} | |
//Hydrators | |
public abstract class ContactHydratorBase | |
{ | |
protected static readonly string _typeName; | |
static ContactHydratorBase() | |
{ | |
var type = typeof(Contact); | |
_typeName = type.FullName; | |
} | |
public static PropertyToValueCorrelation[] GetPropertiesValuesWithoutLinq(DefaultRawStringParser normalizer, string rawData) | |
{ | |
var result = new List<PropertyToValueCorrelation>(10); | |
var mailPairs = normalizer.ParseWithoutLinq(rawData: rawData, pairDelimiter: Environment.NewLine); | |
var mapSchemas = MockHelper.GetFakeData; | |
foreach (var item in mapSchemas) | |
{ | |
if (!item.EntityName.Equals(_typeName, StringComparison.InvariantCultureIgnoreCase)) | |
continue; | |
foreach (var pair in mailPairs) | |
{ | |
if (!pair.Key.Equals(item.Key, StringComparison.InvariantCultureIgnoreCase)) | |
continue; | |
result.Add(new PropertyToValueCorrelation { PropertyName = item.Property, Value = pair.Value }); | |
} | |
} | |
return result.ToArray(); | |
} | |
public abstract Contact GetContact(PropertyToValueCorrelation[] correlation); | |
} | |
public class FastContactHydrator : ContactHydratorBase | |
{ | |
private static readonly List<KeyValuePair<string, Action<Contact, string>>> _proprtySettersMap = | |
new List<KeyValuePair<string, Action<Contact, string>>>(); | |
static FastContactHydrator() | |
{ | |
var type = typeof(Contact); | |
foreach (var property in type.GetProperties()) | |
{ | |
_proprtySettersMap.Add(new KeyValuePair<string, Action<Contact, string>>(property.Name, GetSetterAction(property))); | |
} | |
} | |
private static Action<Contact, string> GetSetterAction(PropertyInfo property) | |
{ | |
var setterInfo = property.GetSetMethod(); | |
var paramValueOriginal = Expression.Parameter(property.PropertyType, "value"); | |
var paramEntity = Expression.Parameter(typeof(Contact), "entity"); | |
var setterExp = Expression.Call(paramEntity, setterInfo, paramValueOriginal).Reduce(); | |
var lambda = (Expression<Action<Contact, string>>)Expression.Lambda(setterExp, paramEntity, paramValueOriginal); | |
return lambda.Compile(); | |
} | |
public override Contact GetContact(PropertyToValueCorrelation[] correlations) | |
{ | |
var contact = new Contact(); | |
foreach (var setterMapItem in _proprtySettersMap) | |
{ | |
var correlation = correlations.FirstOrDefault(x => x.PropertyName == setterMapItem.Key); | |
setterMapItem.Value(contact, correlation?.Value); | |
} | |
return contact; | |
} | |
} | |
public class SlowContactHydrator : ContactHydratorBase | |
{ | |
protected static readonly PropertyInfo[] _properties; | |
static SlowContactHydrator() | |
{ | |
_properties = typeof(Contact).GetProperties(); | |
} | |
public override Contact GetContact(PropertyToValueCorrelation[] correlations) | |
{ | |
var contact = new Contact(); | |
foreach (var property in _properties) | |
{ | |
var correlation = correlations.FirstOrDefault(x => x.PropertyName == property.Name); | |
if (correlation?.Value == null) | |
continue; | |
property.SetValue(contact, correlation.Value); | |
} | |
return contact; | |
} | |
} | |
//Model | |
public class Contact | |
{ | |
public string FullName { get; set; } | |
public string Phone { get; set; } | |
public string Age { get; set; } | |
} | |
public class ContactMapSchema | |
{ | |
public string EntityName { get; set; } | |
public string Key { get; set; } | |
public string Property { get; set; } | |
} | |
public class PropertyToValueCorrelation | |
{ | |
public string PropertyName { get; set; } | |
public string Value { get; set; } | |
} | |
//Parser | |
public class DefaultRawStringParser | |
{ | |
private static readonly string _unrecognizedKey = "Unrecognized"; | |
public Dictionary<string, string> ParseWithoutLinq(string rawData, string keyValueDelimiter = ":", string pairDelimiter = ";") | |
{ | |
var result = new Dictionary<string, string>(); | |
var splitted = rawData?.Split(pairDelimiter); | |
foreach (var item in splitted) | |
{ | |
var pair = item.Split(keyValueDelimiter, StringSplitOptions.RemoveEmptyEntries); | |
if (pair.Length == 2) | |
result.Add(pair[0].Trim(), pair[1].Trim()); | |
else | |
result.Add(_unrecognizedKey, pair[0].Trim()); | |
} | |
return result; | |
} | |
} | |
public static class MockHelper | |
{ | |
public static List<ContactMapSchema> GetFakeData | |
=> new List<ContactMapSchema> | |
{ | |
new ContactMapSchema { EntityName = _contactAssemblyName, Key = "Телефон", Property = _contactPhoneAssemblyName}, | |
new ContactMapSchema { EntityName = _contactAssemblyName, Key = "Контактный телефон", Property = _contactPhoneAssemblyName}, | |
new ContactMapSchema { EntityName = _contactAssemblyName, Key = "Тел.", Property = _contactPhoneAssemblyName}, | |
new ContactMapSchema { EntityName = _contactAssemblyName, Key = "ФИО", Property = _contactFullNameAssemblyName}, | |
new ContactMapSchema { EntityName = _contactAssemblyName, Key = "Фамилия Имя Отчество", Property = _contactFullNameAssemblyName}, | |
new ContactMapSchema { EntityName = _contactAssemblyName, Key = "Имя", Property = _contactFullNameAssemblyName}, | |
new ContactMapSchema { EntityName = _contactAssemblyName, Key = "Возраст", Property = _contactAgeAssemblyName}, | |
new ContactMapSchema { EntityName = _contactAssemblyName, Key = "Полных лет", Property = _contactAgeAssemblyName}, | |
}; | |
private static string _contactAssemblyName = typeof(Contact).FullName; | |
private static string _contactPhoneAssemblyName = nameof(Contact.Phone); | |
private static string _contactFullNameAssemblyName = nameof(Contact.FullName); | |
private static string _contactAgeAssemblyName = nameof(Contact.Age); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment