Skip to content

Instantly share code, notes, and snippets.

@paxbun
Last active July 30, 2021 06:26
Show Gist options
  • Select an option

  • Save paxbun/ca67eaa31b56fdb1bb42693c8173253e to your computer and use it in GitHub Desktop.

Select an option

Save paxbun/ca67eaa31b56fdb1bb42693c8173253e to your computer and use it in GitHub Desktop.
JS spread operator on C#
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
public record Foo
{
public int Data { get; init; }
public int Data2 { get; init; }
public int Data3 { get; init; }
public int Data4 { get; init; }
public int Data5 { get; init; }
public int Data6 { get; init; }
public int Data7 { get; init; }
public int Data8 { get; init; }
public int Data9 { get; init; }
public int Data10 { get; init; }
public int Data11 { get; init; }
public int Data12 { get; init; }
public int Data13 { get; init; }
public int Data14 { get; init; }
public int Data15 { get; init; }
public int Data16 { get; init; }
public int Data17 { get; init; }
public int Data18 { get; init; }
public int Data19 { get; init; }
public int Data20 { get; init; }
public int Data21 { get; init; }
public int Data22 { get; init; }
public int Data23 { get; init; }
public int Data24 { get; init; }
}
public record Bar : Foo
{
public string StringData { get; init; }
}
public static class Extensions
{
private static readonly Dictionary<(Type, Type), Func<object, object>> _propertyCopyDictionary = new();
public static TDestination Copy<TDestination>(this object source)
where TDestination : new()
{
var sourceType = source.GetType();
var destinationType = typeof(TDestination);
Func<object, object> copy;
lock (_propertyCopyDictionary)
{
if (!_propertyCopyDictionary.TryGetValue((sourceType, destinationType), out copy))
{
var sourceParam = Expression.Parameter(typeof(object));
var sourceCasted = Expression.TypeAs(sourceParam, sourceType);
var destination = Expression.New(destinationType);
var sourceTypeProperties = sourceType.GetProperties();
List<MemberBinding> bindings = new(sourceTypeProperties.Length);
foreach (var sourceProperty in sourceTypeProperties)
{
var destinationProperty = destinationType.GetProperty(sourceProperty.Name);
if (destinationProperty is null)
continue;
var sourceGetMethod = sourceProperty.GetGetMethod();
if (sourceGetMethod is null)
continue;
bindings.Add(Expression.Bind(destinationProperty, Expression.Call(
sourceCasted,
sourceGetMethod
)));
}
copy = Expression.Lambda<Func<object, object>>(
Expression.MemberInit(destination, bindings),
sourceParam
).Compile();
_propertyCopyDictionary.Add((sourceType, destinationType), copy);
}
}
return (TDestination)copy(source);
}
public static TDestination Copy2<TDestination>(this object source)
{
var sourceType = source.GetType();
var destinationType = typeof(TDestination);
TDestination destination = (TDestination)Activator.CreateInstance(destinationType);
foreach (var property in sourceType.GetProperties())
{
var destinationProperty = destinationType.GetProperty(property.Name);
destinationProperty.SetValue(destination, property.GetValue(source));
}
return destination;
}
public static Bar Copy3(this Foo foo)
{
return new Bar
{
Data = foo.Data,
Data2 = foo.Data2,
Data3 = foo.Data3,
Data4 = foo.Data4,
Data5 = foo.Data5,
Data6 = foo.Data6,
Data7 = foo.Data7,
Data8 = foo.Data8,
Data9 = foo.Data9,
Data10 = foo.Data10,
Data11 = foo.Data11,
Data12 = foo.Data12,
Data13 = foo.Data13,
Data14 = foo.Data14,
Data15 = foo.Data15,
Data16 = foo.Data16,
Data17 = foo.Data17,
Data18 = foo.Data18,
Data19 = foo.Data19,
Data20 = foo.Data20,
Data21 = foo.Data21,
Data22 = foo.Data22,
Data23 = foo.Data23,
Data24 = foo.Data24,
};
}
private static class Converter<TSource, TDestination>
{
public static Func<TSource, TDestination> Convert { get; private set; }
static Converter()
{
var sourceType = typeof(TSource);
var destinationType = typeof(TDestination);
var sourceParam = Expression.Parameter(sourceType);
var destination = Expression.New(destinationType);
var sourceTypeProperties = sourceType.GetProperties();
List<MemberBinding> bindings = new(sourceTypeProperties.Length);
foreach (var sourceProperty in sourceTypeProperties)
{
var destinationProperty = destinationType.GetProperty(sourceProperty.Name);
if (destinationProperty is null)
continue;
var sourceGetMethod = sourceProperty.GetGetMethod();
if (sourceGetMethod is null)
continue;
bindings.Add(Expression.Bind(destinationProperty, Expression.Call(
sourceParam,
sourceGetMethod
)));
}
Convert = Expression.Lambda<Func<TSource, TDestination>>(
Expression.MemberInit(destination, bindings),
sourceParam
).Compile();
}
}
public static TDestination Copy4<TSource, TDestination>(this TSource source)
{
return Converter<TSource, TDestination>.Convert(source);
}
}
public class Program
{
public static void Main()
{
Foo foo = new Foo
{
Data = 5,
Data3 = 123
};
Bar bar = new Bar
{
Data = 5,
Data3 = 123,
StringData = "Hello"
};
Console.WriteLine(foo);
{
DateTime start = DateTime.Now;
if (!bar.Equals(foo.Copy<Bar>() with { StringData = "Hello" }))
throw new NotImplementedException();
for (int i = 0; i < 10_000_000; ++i)
{
Bar newBar = foo.Copy<Bar>() with { StringData = "Hello" };
}
Console.WriteLine(DateTime.Now - start);
}
{
DateTime start = DateTime.Now;
if (!bar.Equals(foo.Copy2<Bar>() with { StringData = "Hello" }))
throw new NotImplementedException();
for (int i = 0; i < 10_000_000; ++i)
{
Bar newBar = foo.Copy2<Bar>() with { StringData = "Hello" };
}
Console.WriteLine(DateTime.Now - start);
}
{
DateTime start = DateTime.Now;
if (!bar.Equals(foo.Copy3() with { StringData = "Hello" }))
throw new NotImplementedException();
for (int i = 0; i < 10_000_000; ++i)
{
Bar newBar = foo.Copy3() with { StringData = "Hello" };
}
Console.WriteLine(DateTime.Now - start);
}
{
DateTime start = DateTime.Now;
if (!bar.Equals(foo.Copy4<Foo, Bar>() with { StringData = "Hello" }))
throw new NotImplementedException();
for (int i = 0; i < 10_000_000; ++i)
{
Bar newBar = foo.Copy4<Foo, Bar>() with { StringData = "Hello" };
}
Console.WriteLine(DateTime.Now - start);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment