리플렉션을 사용하지 않고 컴파일된 람다를 동적으로 생성해 특정 속성 값을 설정하는 방법과 성능비교를 하는 게시물입니다.
2개의 좋아요
좋은 내용 감사합니다.
그런데 코드가 잘 눈에 들어오지 않아 간단히 정리해봤습니다.
- 코드의 내용은 같지만 일부 코드를 보기 쉽도록 형식을 바꿨으니 참고 부탁드립니다.
이 글은 아래 예제를 기준으로 3가지 방법과 그 성능에 대해 안내하고 있습니다
(혹시 몰라 결론은 가져오지 않았으니 성능비교 결과는 원문에서 확인 부탁드립니다).
- 리플렉션만 이용한 방법
- 리플렉션과 캐싱을 함께 이용한 방법
- 컴파일된 람다식을 이용한 방법
public abstract class Setter
{
public abstract void Set(object obj, string propertyName, object value);
}
리플렉션만 이용한 방법
public sealed class ReflectionSetter : Setter
{
public override void Set(object obj, string propertyName, object value)
{
ArgumentNullException.ThrowIfNull(obj);
ArgumentNullException.ThrowIfNull(propertyName);
var property = obj.GetType().GetProperty(
propertyName,
BindingFlags.Instance
| BindingFlags.Public
| BindingFlags.GetProperty
| BindingFlags.SetProperty);
if (property != null)
{
property.SetValue(obj, value, null);
}
else
{
throw new InvalidOperationException("Property not found.");
}
}
}
리플렉션과 캐싱을 함께 이용한 방법
public sealed class CachedReflectionSetter : Setter
{
private readonly Dictionary<Type, Dictionary<string, PropertyInfo>> _properties = new();
public void Initialize(Type type)
{
ArgumentNullException.ThrowIfNull(type);
_properties[type] = new Dictionary<string, PropertyInfo>();
var properties= type.GetProperties(
BindingFlags.Instance
| BindingFlags.Public
| BindingFlags.GetProperty
| BindingFlags.SetProperty);
foreach (var prop in properties)
{
_properties[type][prop.Name] = prop;
}
}
public override void Set(object obj, string propertyName, object value)
{
ArgumentNullException.ThrowIfNull(obj);
ArgumentNullException.ThrowIfNull(propertyName);
var property = GetPropertyFor(obj.GetType(), propertyName);
if (property != null)
{
property.SetValue(obj, value, null);
}
else
{
throw new InvalidOperationException("Property not found.");
}
}
private PropertyInfo? GetPropertyFor(Type type, string propertyName)
{
if (_properties.TryGetValue(type, out var properties))
{
if (properties.TryGetValue(propertyName, out var prop))
{
return prop;
}
}
return null;
}
}
컴파일된 람다식을 이용한 방법
public sealed class CompiledSetter : Setter
{
private readonly Dictionary<Type, Dictionary<string, Delegate>> _properties = new();
public void Initialize(Type type)
{
ArgumentNullException.ThrowIfNull(type);
_properties[type] = new Dictionary<string, Delegate>();
var properties = type.GetProperties(
BindingFlags.Instance
| BindingFlags.Public
| BindingFlags.GetProperty
| BindingFlags.SetProperty);
foreach (var prop in properties)
{
GenerateSetterFor(type, prop);
}
}
public override void Set(object obj, string propertyName, object value)
{
ArgumentNullException.ThrowIfNull(obj);
ArgumentNullException.ThrowIfNull(propertyName);
var action = GetActionFor(obj.GetType(), propertyName);
if (action is Action<object, object> act)
{
act(obj, value);
}
else
{
throw new InvalidOperationException("Property not found.");
}
}
private void GenerateSetterFor(Type type, PropertyInfo property)
{
var propertyName = property.Name;
var propertyType = property.PropertyType;
var parmExpression = Expression.Parameter(typeof(object), "it");
var castExpression = Expression.Convert(parmExpression, type);
var propertyExpression = Expression.Property(castExpression, propertyName);
var valueExpression = Expression.Parameter(typeof(object), propertyName);
var operationExpression = Expression.Assign(propertyExpression, Expression.Convert(valueExpression, propertyType));
var lambdaExpression = Expression.Lambda(typeof(Action<,>).MakeGenericType(typeof(object), typeof(object)), operationExpression, parmExpression, valueExpression);
var action = lambdaExpression.Compile();
_properties[type][propertyName] = action;
}
private Delegate? GetActionFor(Type type, string propertyName)
{
if (_properties.TryGetValue(type, out var properties))
{
if (properties.TryGetValue(propertyName, out var action))
{
return action;
}
}
return null;
}
}
2개의 좋아요
그런데 댓글도 꽤 재밌네요,
어떤 분은 자기가 여러가지 샘플을 준비했다고 링크를 달아주셨고,
또 어떤 분은 이 방법보다 FastMember가 더 빠르다고 하네요
1개의 좋아요
제 컴퓨터에서는 성능 측정이 이렇게 나왔습니다.
캐시된 리플렉션 속도가 의외인데요, 반대로 이야기 하자면 .NET 6의 리플릭션 속도가 많이 개선되었다고 봐도 될 듯 하군요.
1개의 좋아요
흠 신기하네요. 제네릭 버젼으로 하면 좀 더 빨라질까 해서 테스트를 해봤는데 되려 조금 더 느려집니다.
public override void Set<T, TValue>(T obj, string propertyName, TValue value)
private void GenerateSetterFor(PropertyInfo property, Type type)
{
var propertyName = property.Name;
var propertyType = property.PropertyType;
var parmExpression = Expression.Parameter(type, "it");
var propertyExpression = Expression.Property(parmExpression, propertyName);
var valueExpression = Expression.Parameter(propertyType, propertyName);
var operationExpression = Expression.Assign(propertyExpression, valueExpression);
var lambdaExpression = Expression.Lambda(typeof(Action<,>).MakeGenericType(type, propertyType), operationExpression, parmExpression, valueExpression);
var action = lambdaExpression.Compile();
this._properties[type][propertyName] = action;
}
대략 4us 정도 증가합니다. TestCompiled뿐만 아니라 TestReflection 및 TestCachedReflection 모두 다같이 증가하는것으로 보아 제네릭 Set 구조로 인한것 같네요.
