Last active
July 30, 2021 06:26
-
-
Save paxbun/ca67eaa31b56fdb1bb42693c8173253e to your computer and use it in GitHub Desktop.
JS spread operator on C#
This file contains hidden or 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 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