├── _config.yml ├── image └── logo1.jpg ├── article ├── README.md └── DesignDraft.md ├── src └── DeepClone │ ├── Extension │ └── TypeExtension.cs │ ├── Model │ ├── ICloneTemplate.cs │ └── NeedCtorAttribute.cs │ ├── Builder │ ├── ObjectCloneBuilder.cs │ └── CloneBuilder.cs │ ├── Template │ ├── CloneTemplate.cs │ ├── List │ │ └── CloneListTemplate.cs │ ├── Dictionary │ │ └── CloneDictTemplate.cs │ ├── Class │ │ ├── CtorTempalte.cs │ │ └── CloneClassTemplate.cs │ └── Array │ │ └── CloneArrayTemplate.cs │ ├── DeepClone.csproj │ ├── CloneOperator.cs │ └── ObjectCloneOperator.cs ├── Directory.Build.props ├── test └── DeepCloneUT │ ├── Prepare.cs │ ├── OnceType │ └── Enums.cs │ ├── Model │ ├── ReverserTestModel.cs │ ├── OopTestModel.cs │ ├── RemoteTestModel.cs │ ├── StaticTestModel.cs │ ├── ProxyStandard.cs │ └── CloneTestModel.cs │ ├── DeepCloneUT.csproj │ ├── DictionaryTest.cs │ ├── ObjectTest.cs │ ├── EqualityComparer │ ├── MemberArrayEqualityComparer.cs │ ├── ListArrayEqualityComparer.cs │ └── DictArrayEqualityComparer.cs │ ├── TestFromMoney.cs │ ├── ListTest.cs │ ├── ComplexType │ └── Member.cs │ ├── SubclassTest.cs │ ├── ClassTest.cs │ ├── Mocker.cs │ ├── NatashaUtTest.cs │ └── ArrayTest.cs ├── samples └── DeepCloneSelfTest │ ├── DeepCloneSelfTest.csproj │ ├── Test.cs │ ├── Program.cs │ └── Model │ └── Member.cs ├── .travis.yml ├── LICENSE ├── appveyor.yml ├── .github └── workflows │ └── dotnetcore.yml ├── azure-pipelines.yml ├── DeepClone.sln ├── README.md └── .gitignore /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-minimal -------------------------------------------------------------------------------- /image/logo1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/night-moon-studio/DeepClone/HEAD/image/logo1.jpg -------------------------------------------------------------------------------- /article/README.md: -------------------------------------------------------------------------------- 1 | # DeepClone 系列文章 2 | 3 | ## [设计草稿](https://github.com/night-moon-studio/DeepClone/blob/master/article/DesignDraft.md) 4 | -------------------------------------------------------------------------------- /src/DeepClone/Extension/TypeExtension.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace DeepClone.Extension 6 | { 7 | public class TypeExtension 8 | { 9 | 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/DeepClone/Model/ICloneTemplate.cs: -------------------------------------------------------------------------------- 1 | using Natasha; 2 | using System; 3 | 4 | namespace DeepClone.Model 5 | { 6 | public interface ICloneTemplate 7 | { 8 | 9 | Delegate TypeRouter(NBuildInfo info); 10 | 11 | bool MatchType(Type type); 12 | 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | copyright © .NET Core Community 4 | DeepClone 5 | .NET Core Community 6 | All Members 7 | All Members 8 | 9 | -------------------------------------------------------------------------------- /test/DeepCloneUT/Prepare.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace DeepCloneUT 6 | { 7 | public class Prepare 8 | { 9 | static Prepare() 10 | { 11 | NatashaInitializer.InitializeAndPreheating(); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/DeepClone/Model/NeedCtorAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace DeepClone 4 | { 5 | 6 | public class NeedCtorAttribute:Attribute 7 | { 8 | public string Name; 9 | public NeedCtorAttribute() 10 | { 11 | 12 | } 13 | public NeedCtorAttribute(string name) 14 | { 15 | Name = name; 16 | } 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /test/DeepCloneUT/OnceType/Enums.cs: -------------------------------------------------------------------------------- 1 | namespace DeepCloneUT 2 | { 3 | /// 4 | /// Gender Enum 5 | /// 6 | public enum GenderEnum 7 | { 8 | Secrecy = 0, 9 | Male, 10 | Female 11 | } 12 | 13 | /// 14 | /// Member Type 15 | /// 16 | public enum MemberType 17 | { 18 | Student = 0, 19 | Teacher 20 | } 21 | } -------------------------------------------------------------------------------- /samples/DeepCloneSelfTest/DeepCloneSelfTest.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp2.2 6 | true 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /samples/DeepCloneSelfTest/Test.cs: -------------------------------------------------------------------------------- 1 | using Natasha; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace DeepCloneSelfTest 7 | { 8 | public class Test 9 | { 10 | public string Name; 11 | public string Age 12 | { 13 | get; 14 | } 15 | 16 | public void Clone() 17 | { 18 | 19 | 20 | 21 | 22 | 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /test/DeepCloneUT/Model/ReverserTestModel.cs: -------------------------------------------------------------------------------- 1 | namespace NatashaUT.Model 2 | { 3 | public class ReverserTestModel 4 | { 5 | public void Test1(in Rsm rsm) { } 6 | public void Test2(out Rsm[]> rsm) { rsm = new Rsm[]>(); } 7 | public void Test3(ref Rsm[]>[] rsm) { } 8 | } 9 | 10 | public class Rsm { 11 | 12 | } 13 | 14 | public class GRsm 15 | { 16 | 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /test/DeepCloneUT/Model/OopTestModel.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace NatashaUT.Model 4 | { 5 | class OopTestModel 6 | { 7 | 8 | public void ReWrite1() 9 | { 10 | 11 | } 12 | public async Task ReWrite2() 13 | { 14 | return this; 15 | } 16 | 17 | public virtual void ReWrite3(ref int i, string temp) 18 | { 19 | 20 | } 21 | 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/DeepClone/Builder/ObjectCloneBuilder.cs: -------------------------------------------------------------------------------- 1 | using Natasha; 2 | using Natasha.CSharp; 3 | using System; 4 | 5 | namespace DeepClone.Builder 6 | { 7 | 8 | public static class ObjectCloneBuilder 9 | { 10 | 11 | public static Func Create(Type type) 12 | { 13 | return NDelegate.UseDomain(type.GetDomain()).Func($"return CloneOperator.Clone(({type.GetDevelopName()})arg);", type, "DeepClone"); 14 | } 15 | 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: csharp 2 | solution: DeepClone.sln 3 | dist: xenial 4 | dotnet: 2.2.300 5 | mono: none 6 | 7 | branches: 8 | only: 9 | - master 10 | 11 | os: 12 | - linux 13 | - osx 14 | 15 | osx_image: xcode9.1 16 | 17 | before_install: 18 | 19 | #env: 20 | #global: 21 | #- DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true 22 | #- DOTNET_CLI_TELEMETRY_OPTOUT: 1 23 | 24 | 25 | install: 26 | 27 | 28 | script: 29 | - dotnet restore 30 | - dotnet build -c Release 31 | - dotnet test 32 | -------------------------------------------------------------------------------- /test/DeepCloneUT/Model/RemoteTestModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace NatashaUT.Model 4 | { 5 | public class RemoteTestModel 6 | { 7 | public void Say() 8 | { 9 | Console.WriteLine("hello world"); 10 | } 11 | 12 | public string HelloString(string str1,string str2) 13 | { 14 | return str1 + str2; 15 | } 16 | public int HelloInt(int str1, int str2) 17 | { 18 | return str1 + str2; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test/DeepCloneUT/DeepCloneUT.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1;netcoreapp3.0; 5 | true 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | all 14 | runtime; build; native; contentfiles; analyzers; buildtransitive 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/DeepClone/Builder/CloneBuilder.cs: -------------------------------------------------------------------------------- 1 | using DeepClone.Template; 2 | using Natasha; 3 | using System; 4 | using System.Collections; 5 | 6 | namespace DeepClone.Builder 7 | { 8 | 9 | public static class CloneBuilder 10 | { 11 | public static Func Create(Type realType) 12 | { 13 | 14 | return (Func)(new FullCloneBuilder()).Create(realType); 15 | 16 | } 17 | } 18 | 19 | 20 | public class FullCloneBuilder : CloneTemplate 21 | { 22 | public FullCloneBuilder() : base() 23 | { 24 | Register(); 25 | Register(); 26 | Register(); 27 | Register(); 28 | } 29 | 30 | public Delegate Create(Type type) 31 | { 32 | NBuildInfo info = type; 33 | info.FatherType = typeof(T); 34 | return TypeRouter(info); 35 | } 36 | 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /test/DeepCloneUT/Model/StaticTestModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace NatashaUT.Model 4 | { 5 | public static class StaticTestModel 6 | { 7 | public static int Age; 8 | public static string Name { get; set; } 9 | 10 | public static DateTime Temp; 11 | 12 | public static float Money; 13 | } 14 | 15 | public class FakeStaticTestModel 16 | { 17 | public static int Age; 18 | public static string Name { get; set; } 19 | 20 | public static DateTime Temp; 21 | 22 | public static float Money; 23 | } 24 | 25 | public static class StaticTestModel1 26 | { 27 | public static int Age; 28 | public static string Name { get; set; } 29 | 30 | public static DateTime Temp; 31 | 32 | public static float Money; 33 | } 34 | 35 | public class FakeStaticTestModel1 36 | { 37 | public static int Age; 38 | public static string Name { get; set; } 39 | 40 | public static DateTime Temp; 41 | 42 | public static float Money; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Night Moon Studio 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /test/DeepCloneUT/DictionaryTest.cs: -------------------------------------------------------------------------------- 1 | using DeepClone; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using Xunit; 7 | 8 | namespace DeepCloneUT 9 | { 10 | public class DictionaryTest : Prepare 11 | { 12 | [Fact] 13 | public void DictionaryCloneTest() 14 | { 15 | var dict = new Dictionary>() { { "1", new List { 1} } }; 16 | var dictNew = CloneOperator.Clone(dict); 17 | Assert.NotNull(dictNew); 18 | Assert.NotSame(dict, dictNew); 19 | Assert.True(dict.Count == dictNew.Count); 20 | Assert.NotNull(dictNew["1"]); 21 | Assert.NotSame(dict["1"], dictNew["1"]); 22 | 23 | var dictModel = new Dictionary { { new TestModel(),1 } }; 24 | var dictModelNew = CloneOperator.Clone(dictModel); 25 | Assert.NotNull(dictModelNew); 26 | Assert.NotSame(dictModel, dictModelNew); 27 | Assert.True(dictModel.Count == dictModelNew.Count); 28 | Assert.NotNull(dictModelNew.Keys); 29 | Assert.NotSame(dictModel.Keys.FirstOrDefault(), dictModelNew.Keys.FirstOrDefault()); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/DeepClone/Template/CloneTemplate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using DeepClone.Model; 3 | using System.Collections.Generic; 4 | using Natasha; 5 | 6 | namespace DeepClone.Template 7 | { 8 | public class CloneTemplate : ICloneTemplate 9 | { 10 | 11 | 12 | private readonly HashSet _handlers; 13 | public CloneTemplate() 14 | { 15 | _handlers = new HashSet(); 16 | } 17 | 18 | 19 | 20 | 21 | public bool MatchType(Type type) 22 | { 23 | throw new NotImplementedException(); 24 | } 25 | 26 | 27 | 28 | 29 | /// 30 | /// 注册处理类 31 | /// 32 | /// 33 | /// 34 | public CloneTemplate Register() where T: ICloneTemplate,new() 35 | { 36 | _handlers.Add(new T()); 37 | return this; 38 | } 39 | 40 | 41 | 42 | 43 | 44 | public Delegate TypeRouter(NBuildInfo info) 45 | { 46 | 47 | foreach (var item in _handlers) 48 | { 49 | if (item.MatchType(info.CurrentType)) 50 | { 51 | return item.TypeRouter(info); 52 | } 53 | } 54 | return default; 55 | 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/DeepClone/DeepClone.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 基于Natasha实现深度克隆,包括实体类、数组、集合、链表、字典等数据类型。 6 | 7 | NMS.DeepClone 8 | False 9 | CloneOperator.Clone(instance); 10 | 1.0.0.1 11 | https://github.com/night-moon-studio/DeepClone/blob/master/LICENSE 12 | https://github.com/night-moon-studio/DeepClone 13 | DeepClone; Natasha; Roslyn;NMS; 14 | True 15 | https://avatars2.githubusercontent.com/u/51699821 16 | 17 | 18 | 19 | true 20 | 21 | 22 | 23 | true 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: 1.0.0.{build} 2 | image: 3 | - Ubuntu1804 4 | - Visual Studio 2019 5 | 6 | 7 | 8 | 9 | configuration: Release 10 | clone_depth: 50 11 | 12 | pull_requests: 13 | do_not_increment_build_number: true 14 | 15 | branches: 16 | only: 17 | - master 18 | #skip_branch_with_pr: true 19 | skip_commits: 20 | files: 21 | - docs/* 22 | - Image/* 23 | - media/* 24 | - LICENSE 25 | - mkdocs.yml 26 | - README.md 27 | - .travis.yml 28 | - .gitignore 29 | - azure-pipelines.yml 30 | - lang/* 31 | 32 | for: 33 | - 34 | matrix: 35 | only: 36 | - image: Visual Studio 2019 37 | 38 | 39 | install: 40 | #- ps: cd c:\projects\xxx 41 | 42 | before_build: 43 | - ps: dotnet restore 44 | - ps: choco install opencover.portable 45 | - ps: choco install codecov 46 | 47 | build_script: 48 | - ps: dotnet build -c Release 49 | 50 | 51 | test_script: 52 | - ps: OpenCover.Console.exe -register:user -target:"dotnet.exe" -targetargs:test -output:".\coverage.xml" -oldstyle 53 | - ps: codecov -f "coverage.xml" -t %COVERAGE_DEEPCLONE_KEY% 54 | 55 | - 56 | matrix: 57 | only: 58 | - image: Ubuntu1804 59 | 60 | services: 61 | 62 | 63 | install: 64 | 65 | 66 | before_build: 67 | - sh: dotnet restore 68 | 69 | build_script: 70 | - sh: dotnet build -c Release 71 | 72 | test_script: 73 | - ps: dotnet test -------------------------------------------------------------------------------- /src/DeepClone/CloneOperator.cs: -------------------------------------------------------------------------------- 1 | using DeepClone.Builder; 2 | using DynamicCache; 3 | using System; 4 | using System.Collections.Concurrent; 5 | 6 | namespace DeepClone 7 | { 8 | public static class CloneOperator 9 | { 10 | 11 | public static T Clone(T instance) where T: class 12 | { 13 | 14 | return CloneOperator.Clone(instance); 15 | 16 | } 17 | 18 | } 19 | 20 | 21 | 22 | public static class CloneOperator where T: class 23 | { 24 | 25 | public static HashCache> CloneMapping; 26 | public readonly static ConcurrentDictionary> _mapping_cache; 27 | static CloneOperator() 28 | { 29 | 30 | _mapping_cache = new ConcurrentDictionary>(); 31 | CloneMapping = _mapping_cache.HashTree(); 32 | 33 | } 34 | 35 | 36 | 37 | 38 | public unsafe static T Clone(T instance) 39 | { 40 | 41 | if (instance == default) 42 | { 43 | return default; 44 | } 45 | 46 | 47 | Type type = instance.GetType(); 48 | var func = CloneMapping.GetValue(type); 49 | if (func==default) 50 | { 51 | 52 | func = CloneBuilder.Create(type); 53 | _mapping_cache[type] = func; 54 | CloneMapping = _mapping_cache.HashTree(DyanamicCacheDirection.KeyToValue); 55 | } 56 | 57 | return func(instance); 58 | 59 | } 60 | 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /samples/DeepCloneSelfTest/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace DeepCloneSelfTest 4 | { 5 | class Program 6 | { 7 | 8 | static void Main(string[] args) 9 | { 10 | 11 | int[,] a = new int[1, 2]; 12 | for (int i = 0; i < a.GetLength(0); i++) 13 | { 14 | for (int j = 0; j < a.GetLength(1); j++) 15 | { 16 | a[i, j] = j; 17 | Console.WriteLine($"i{i}j{j}:{j}"); 18 | } 19 | } 20 | int[,] b= new int[1,2]; 21 | Array.Copy(a, b, a.Length); 22 | for (int i = 0; i < b.GetLength(0); i++) 23 | { 24 | for (int j = 0; j < b.GetLength(1); j++) 25 | { 26 | a[i, j] = j; 27 | Console.WriteLine($"i{i}j{j}:{j}"); 28 | } 29 | } 30 | 31 | int[][] c = new int[1][]; 32 | for (int i = 0; i < c.Length; i++) 33 | { 34 | c[i] = new int[2]; 35 | for (int j = 0; j < 2; j++) 36 | { 37 | c[i][j] = j+10; 38 | } 39 | } 40 | int[][] d = new int[1][]; 41 | Array.Copy(c, d, c.Length); 42 | for (int i = 0; i < d.Length; i++) 43 | { 44 | for (int j = 0; j < 2; j++) 45 | { 46 | Console.WriteLine(d[i][j]); 47 | } 48 | } 49 | Console.ReadKey(); 50 | 51 | } 52 | 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /test/DeepCloneUT/ObjectTest.cs: -------------------------------------------------------------------------------- 1 | using DeepClone; 2 | using NatashaUT.Model; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | using Xunit; 7 | 8 | namespace DeepCloneUT 9 | { 10 | public class ObjectTest : Prepare 11 | { 12 | public class TestModel 13 | { 14 | public int A { get; set; } 15 | } 16 | 17 | [Fact] 18 | public void ObjectCloneTest() 19 | { 20 | //System.ValueType 21 | Object obj = 1; 22 | var objNew=ObjectCloneOperator.Clone(obj); 23 | Assert.Same(obj, objNew); 24 | Assert.Equal(obj,objNew); 25 | //class 26 | object objA = new TestModel() { A = 2 }; 27 | object objB = ObjectCloneOperator.Clone(objA); 28 | Assert.NotNull(objB); 29 | Assert.NotSame(objA, objB); 30 | Assert.Equal(((TestModel)objA).A, ((TestModel)objB).A); 31 | //dict 32 | object dictA = new Dictionary(); 33 | object dictB = ObjectCloneOperator.Clone(dictA); 34 | Assert.NotNull(dictB); 35 | Assert.NotSame(dictA, dictB); 36 | //list 37 | object listA = new List(); 38 | object listB = ObjectCloneOperator.Clone(listA); 39 | Assert.NotNull(listB); 40 | Assert.NotSame(listA, listB); 41 | //arr 42 | object arrA = new string[0]; 43 | object arrB = ObjectCloneOperator.Clone(arrA); 44 | Assert.NotNull(arrB); 45 | Assert.NotSame(arrA, arrB); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /test/DeepCloneUT/Model/ProxyStandard.cs: -------------------------------------------------------------------------------- 1 | namespace NatashaUT.Model 2 | { 3 | 4 | public interface ITest 5 | { 6 | int MethodWidthReturnInt(); 7 | string MethodWidthReturnString(); 8 | void MethodWidthParamsRefInt(ref int i); 9 | string MethodWidthParamsString(string str); 10 | string MethodWidthParams(int a, string str, out int b); 11 | } 12 | 13 | 14 | 15 | 16 | public abstract class ATest 17 | { 18 | public abstract int MethodWidthReturnInt(); 19 | public abstract string MethodWidthReturnString(); 20 | public abstract void MethodWidthParamsRefInt(ref int i); 21 | public abstract string MethodWidthParamsString(string str); 22 | public abstract string MethodWidthParams(int a, string str,out int b); 23 | } 24 | 25 | 26 | 27 | 28 | public abstract class VTest 29 | { 30 | public virtual int MethodWidthReturnInt() { return default; } 31 | public virtual string MethodWidthReturnString() { return default; } 32 | public virtual void MethodWidthParamsRefInt(ref int i) { } 33 | public virtual string MethodWidthParamsString(string str) { return default; } 34 | public virtual string MethodWidthParams(int a, string str,out int b) { b = default; return default; } 35 | } 36 | 37 | 38 | 39 | 40 | public abstract class NTest 41 | { 42 | public int MethodWidthReturnInt() { return default; } 43 | public string MethodWidthReturnString() { return default; } 44 | public void MethodWidthParamsRefInt(ref int i) { } 45 | public string MethodWidthParamsString(string str) { return default; } 46 | public string MethodWidthParams(int a, string str, out int b) { b = default; return default; } 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /test/DeepCloneUT/EqualityComparer/MemberArrayEqualityComparer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Xunit; 3 | 4 | namespace DeepCloneUT.EqualityComparer 5 | { 6 | public class MemberArrayEqualityComparer : IEqualityComparer 7 | { 8 | public bool Equals(Member x, Member y) 9 | { 10 | if (x == null && y == null) 11 | return true; 12 | return x.Equals(y); 13 | } 14 | 15 | public int GetHashCode(Member obj) 16 | { 17 | throw new System.NotImplementedException(); 18 | } 19 | } 20 | 21 | public class Member1JaggedArrayEqualityComparer : IEqualityComparer 22 | { 23 | public bool Equals(Member[] x, Member[] y) 24 | { 25 | if (x == null && y == null) 26 | return true; 27 | 28 | if (x?.Length != y?.Length) 29 | return false; 30 | 31 | Assert.Equal(x, y, new MemberArrayEqualityComparer()); 32 | return true; 33 | } 34 | 35 | public int GetHashCode(Member[] obj) 36 | { 37 | throw new System.NotImplementedException(); 38 | } 39 | } 40 | 41 | public class Member2JaggedArrayEqualityComparer : IEqualityComparer 42 | { 43 | public bool Equals(Member[][] x, Member[][] y) 44 | { 45 | if (x == null && y == null) 46 | return true; 47 | 48 | if (x?.Length != y?.Length) 49 | return false; 50 | 51 | Assert.Equal(x, y, new Member1JaggedArrayEqualityComparer()); 52 | return true; 53 | } 54 | 55 | public int GetHashCode(Member[][] obj) 56 | { 57 | throw new System.NotImplementedException(); 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /samples/DeepCloneSelfTest/Model/Member.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace DeepCloneSelfTest.Model 4 | { 5 | public class Member 6 | { 7 | private long? _id; 8 | 9 | public long Id 10 | { 11 | get 12 | { 13 | if (_id == null) 14 | { 15 | _id = (long)(DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalMilliseconds; 16 | } 17 | return _id.Value; 18 | } 19 | } 20 | 21 | public MemberType MemberType; 22 | 23 | /// 24 | /// 全名 25 | /// 26 | public string FullName => $"{LastName}{(string.IsNullOrEmpty(MiddleName) ? string.Empty : $"-{MiddleName}")}-{FirstName}"; 27 | 28 | /// 29 | /// 名 30 | /// 31 | public string FirstName { get; set; } 32 | 33 | /// 34 | /// 中间名 35 | /// 36 | public string MiddleName { get; set; } 37 | 38 | /// 39 | /// 姓 40 | /// 41 | public string LastName { get; set; } 42 | 43 | /// 44 | /// 年龄 45 | /// 46 | public int Age { get; set; } 47 | 48 | /// 49 | /// 生日 50 | /// 51 | public DateTime? Birthday { get; set; } 52 | 53 | /// 54 | /// 年收入 55 | /// 56 | public decimal AnnualIncome { get; set; } 57 | 58 | /// 59 | /// 老师 60 | /// 61 | public Member Teacher { get; set; } 62 | } 63 | 64 | /// 65 | /// 用户类型 66 | /// 67 | public enum MemberType 68 | { 69 | Student = 0, 70 | Teacher 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /test/DeepCloneUT/EqualityComparer/ListArrayEqualityComparer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Xunit; 4 | 5 | namespace DeepCloneUT.EqualityComparer 6 | { 7 | public class ListArrayEqualityComparer : IEqualityComparer> 8 | { 9 | public bool Equals(List x, List y) 10 | { 11 | if (x == null && y == null) 12 | return true; 13 | return x.SequenceEqual(y); 14 | } 15 | 16 | public int GetHashCode(List obj) 17 | { 18 | throw new System.NotImplementedException(); 19 | } 20 | } 21 | 22 | public class List1JaggedArrayEqualityComparer : IEqualityComparer[]> 23 | { 24 | public bool Equals(List[] x, List[] y) 25 | { 26 | if (x == null && y == null) 27 | return true; 28 | 29 | if (x?.Length != y?.Length) 30 | return false; 31 | 32 | Assert.Equal(x, y, new ListArrayEqualityComparer()); 33 | return true; 34 | } 35 | 36 | public int GetHashCode(List[] obj) 37 | { 38 | throw new System.NotImplementedException(); 39 | } 40 | } 41 | 42 | public class List2JaggedArrayEqualityComparer : IEqualityComparer[][]> 43 | { 44 | public bool Equals(List[][] x, List[][] y) 45 | { 46 | if (x == null && y == null) 47 | return true; 48 | 49 | if (x?.Length != y?.Length) 50 | return false; 51 | 52 | Assert.Equal(x, y, new List1JaggedArrayEqualityComparer()); 53 | return true; 54 | } 55 | 56 | public int GetHashCode(List[][] obj) 57 | { 58 | throw new System.NotImplementedException(); 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /article/DesignDraft.md: -------------------------------------------------------------------------------- 1 | # DeepClone项目设计草稿 2 | 3 |
4 | 5 | ## 项目需求 6 | 7 | 以Natasha为动态构建引擎,编写一个深度克隆的组件,提供类与结构体的深度克隆。 8 | 要有完整的UT测试,从广度,到深度。 9 | 10 |
11 |
12 | 13 | 14 | ## 技术难点 15 | 16 | - ### 类型识别: 17 | 18 | - OnceType(简单类型): 19 | 20 | - 基元类型:即内置类型,例如int,long,float,string等等 21 | - 特殊类型:decimal 22 | - 枚举类型:enum 23 | 24 | 25 | - 数组类型: 26 | 27 | - 一维数组 28 | - 多维数组 29 | 30 | - 集合类型: 31 | 32 | - 链表 33 | - 集合 34 | - 列表 35 | 36 | - 键值对类型: 37 | 38 | - 字典 39 | - 并发字典 40 | - KeyAndValue 41 | 42 | - 普通实体类 43 | 44 | - 结构体类型 45 | 46 | - 自定义结构体 47 | - 元组 48 | 49 |
50 | 51 | - ### Readonly字段 52 | 53 | readonly 字段通常由初始化获得,因此我们需要对构造函数进行挑选,并在克隆的时候进行传参处理。 54 | 55 |
56 | 57 | - ### 属性读写权限 58 | 59 | 在遍历成员时,需要过滤一下带有特殊读写权限的属性。 60 | 61 |
62 | 63 | - ### 高效极简操作: 64 | 65 | 在数组copy时,可以使用ArrayCopy、Memory.Copy、ReadonlySpan等方法,需要调研。 66 | 67 |
68 | 69 | - ### Object类型: 70 | 71 | Object类型是干扰泛型类型的一个因素,因此是否对Object做支持,如何做,是一个难点。 72 | 73 |
74 | 75 | 76 | - ### 类型反解: 77 | 78 | 在使用Natasha的过程中,需要像写原生代码一样写脚本,但是有些类型的名字在运行时是被处理过的, 79 | 例如:typeof(List).Name, 输出的是List~1, 因此我们需要用到Natasha的反解器,type.GetDevelopmentName(); 80 | 81 |
82 | 83 | 84 | - ### UT测试方法: 85 | 86 | 请区分好Assert.Same/Equal方法,灵活使用。 87 | 88 |
89 |
90 | 91 | ## 成员分工 92 | 93 |
94 |
95 | 96 | ## 开发流程 97 | 98 | 1. 了解需求,分析难点。 99 | 1. 确定各自的开发模块。 100 | 1. 使用运行时模拟模块的工作代码。 101 | 1. 讨论类库架构以及模块的集成方式。 102 | 1. 进入开发阶段,代码、注释、单元测试。 103 | 1. CodeReview。 104 | 1. 补充文档,并文档Review。 105 | 106 |
107 |
108 | 109 | ## 后期规划 110 | 111 | 深度克隆是熟悉.NET类型的最好实践,有了此基础,便可以做一些更加有难度的事情。例如结构体快照,实体映射等。 112 | 113 |
114 |
115 | -------------------------------------------------------------------------------- /test/DeepCloneUT/EqualityComparer/DictArrayEqualityComparer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Xunit; 4 | 5 | namespace DeepCloneUT.EqualityComparer 6 | { 7 | public class DictArrayEqualityComparer : IEqualityComparer> 8 | { 9 | public bool Equals(Dictionary x, Dictionary y) 10 | { 11 | if (x == null && y == null) 12 | return true; 13 | return x.Keys.SequenceEqual(y.Keys) && x.Values.SequenceEqual(y.Values); 14 | } 15 | 16 | public int GetHashCode(Dictionary obj) 17 | { 18 | throw new System.NotImplementedException(); 19 | } 20 | } 21 | 22 | public class Dict1JaggedArrayEqualityComparer : IEqualityComparer[]> 23 | { 24 | public bool Equals(Dictionary[] x, Dictionary[] y) 25 | { 26 | if (x == null && y == null) 27 | return true; 28 | 29 | if (x?.Length != y?.Length) 30 | return false; 31 | 32 | Assert.Equal(x, y, new DictArrayEqualityComparer()); 33 | return true; 34 | } 35 | 36 | public int GetHashCode(Dictionary[] obj) 37 | { 38 | throw new System.NotImplementedException(); 39 | } 40 | } 41 | 42 | public class Dict2JaggedArrayEqualityComparer : IEqualityComparer[][]> 43 | { 44 | public bool Equals(Dictionary[][] x, Dictionary[][] y) 45 | { 46 | if (x == null && y == null) 47 | return true; 48 | 49 | if (x?.Length != y?.Length) 50 | return false; 51 | 52 | Assert.Equal(x, y, new Dict1JaggedArrayEqualityComparer()); 53 | return true; 54 | } 55 | 56 | public int GetHashCode(Dictionary[][] obj) 57 | { 58 | throw new System.NotImplementedException(); 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /test/DeepCloneUT/TestFromMoney.cs: -------------------------------------------------------------------------------- 1 | using DeepClone; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using Xunit; 6 | 7 | namespace DeepCloneUT 8 | { 9 | public class FluentSheetMetadata : SheetMetadata, IFluentMetadata 10 | { 11 | public FluentSheetMetadata() 12 | { 13 | 14 | } 15 | 16 | } 17 | 18 | internal interface IFluentMetadata 19 | { 20 | } 21 | 22 | public class FluentFieldMetadata : FieldMetadata, IFluentMetadata 23 | { 24 | public FluentFieldMetadata() 25 | { 26 | 27 | } 28 | } 29 | 30 | public class FieldMetadata 31 | { 32 | public string Name { get; set; } 33 | public string[] Header { get; set; } 34 | public object Value { get; set; } 35 | public bool Required { get; set; } = false; 36 | } 37 | 38 | public class SheetMetadata where TFieldMetadata : FieldMetadata 39 | { 40 | public string Name { get; set; } 41 | public int HeaderRow { get; set; } = 1; 42 | public int DataStartRow { get; set; } = 2; 43 | public bool Required { get; set; } = false; 44 | public Dictionary FieldMetadatas { get; set; } = new Dictionary(); 45 | } 46 | 47 | public interface ITest 48 | { 49 | 50 | } 51 | 52 | 53 | public class TestFromMoney : Prepare 54 | { 55 | 56 | [Fact] 57 | public void FluentSheetMetadataCloneTest() 58 | { 59 | var metadata = new FluentSheetMetadata() 60 | { 61 | Name = "test", 62 | FieldMetadatas = new Dictionary() 63 | { 64 | ["ApplicationName"] = new FluentFieldMetadata() 65 | { Name = "ApplicationName", Header = new[] { "申请单位名称" }, Required = true }, 66 | } 67 | }; 68 | var cloneMetadata = CloneOperator.Clone(metadata); 69 | Assert.NotSame(metadata, cloneMetadata); 70 | 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/DeepClone/ObjectCloneOperator.cs: -------------------------------------------------------------------------------- 1 | using DeepClone.Builder; 2 | using DynamicCache; 3 | using Natasha; 4 | using System; 5 | using System.Collections.Concurrent; 6 | using System.Collections.Generic; 7 | using System.Text; 8 | 9 | namespace DeepClone 10 | { 11 | public static class ObjectCloneOperator 12 | { 13 | 14 | public static HashCache> CloneMapping; 15 | public readonly static ConcurrentDictionary> _methodCache; 16 | static ObjectCloneOperator() 17 | { 18 | _methodCache = new ConcurrentDictionary>(); 19 | CloneMapping = _methodCache.HashTree(); 20 | } 21 | 22 | public unsafe static object Clone(object instance) 23 | { 24 | 25 | if (instance==null) 26 | { 27 | 28 | return null; 29 | 30 | } 31 | else 32 | { 33 | 34 | var type = instance.GetType(); 35 | if (type.IsSimpleType()) 36 | { 37 | 38 | return instance; 39 | 40 | } 41 | else if (type==typeof(object)) 42 | { 43 | 44 | return new object(); 45 | 46 | } 47 | else 48 | { 49 | 50 | var func = CloneMapping.GetValue(type); 51 | if (func==default) 52 | { 53 | 54 | func = ObjectCloneBuilder.Create(type); 55 | _methodCache[type] = func; 56 | int[] code = new int[_methodCache.Count]; 57 | int i = 0; 58 | foreach (var item in _methodCache) 59 | { 60 | code[i] = item.Value.GetHashCode(); 61 | i++; 62 | } 63 | CloneMapping = _methodCache.HashTree(DyanamicCacheDirection.KeyToValue); 64 | 65 | } 66 | return func(instance); 67 | 68 | } 69 | 70 | 71 | } 72 | 73 | } 74 | 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /.github/workflows/dotnetcore.yml: -------------------------------------------------------------------------------- 1 | name: .NET Core 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | linux: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v1 13 | - name: Setup 14 | uses: actions/setup-dotnet@v1 15 | with: 16 | dotnet-version: 3.1.100 17 | - name: Setup DotNet 2.1.4 18 | uses: actions/setup-dotnet@v1 19 | with: 20 | dotnet-version: 2.1.4 21 | - name: Build 22 | run: dotnet build './src/DeepClone/DeepClone.csproj' --configuration Release 23 | - name: Test 24 | run: dotnet test './test/DeepCloneUT' 25 | 26 | windows: 27 | runs-on: windows-latest 28 | steps: 29 | - name : CheckOut Code 30 | uses: actions/checkout@v1 31 | 32 | - name: Setup DotNet 3.1.100 33 | uses: actions/setup-dotnet@v1 34 | with: 35 | dotnet-version: 3.1.100 36 | 37 | - name: Setup DotNet 2.1.4 38 | uses: actions/setup-dotnet@v1 39 | with: 40 | dotnet-version: 2.1.4 41 | 42 | - name: Setup Opencover 43 | uses: crazy-max/ghaction-chocolatey@v1 44 | with: 45 | args: install opencover.portable 46 | env: Setup DotNet 2.1.4 47 | 48 | - name: Setup Cover 49 | uses: crazy-max/ghaction-chocolatey@v1 50 | with: 51 | args: install codecov 52 | 53 | - name: Build 54 | run: dotnet build './src/DeepClone/DeepClone.csproj' --configuration Release 55 | 56 | - name: Test 57 | run: 58 | OpenCover.Console.exe -register:user -target:"dotnet.exe" -targetargs:"test ./test/DeepCloneUT" -output:"./coverage.xml" -oldstyle 59 | 60 | - name: Upload Report 61 | run: 62 | codecov -f "./coverage.xml" -t ${{ secrets.COVERAGE_KEY }} 63 | 64 | mac: 65 | runs-on: macos 66 | steps: 67 | - uses: actions/checkout@v1 68 | - name: Setup 69 | uses: actions/setup-dotnet@v1 70 | with: 71 | dotnet-version: 3.1.100 72 | - name: Setup DotNet 2.1.4 73 | uses: actions/setup-dotnet@v1 74 | with: 75 | dotnet-version: 2.1.4 76 | - name: Build 77 | run: dotnet build './src/DeepClone/DeepClone.csproj' --configuration Release 78 | - name: Test 79 | run: dotnet test './test/DeepCloneUT' 80 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | # .NET Desktop 2 | # Build and run tests for .NET Desktop or Windows classic desktop solutions. 3 | # Add steps that publish symbols, save build artifacts, and more: 4 | # https://docs.microsoft.com/azure/devops/pipelines/apps/windows/dot-net 5 | 6 | trigger: 7 | branches: 8 | include: 9 | - master 10 | paths: 11 | exclude: 12 | - README.md 13 | - appveyor.yml 14 | - .travis.yml 15 | - lang/* 16 | 17 | jobs: 18 | 19 | 20 | - job: Linux 21 | pool: 22 | vmImage: 'ubuntu-16.04' 23 | 24 | 25 | steps: 26 | - task: DotNetCoreInstaller@0 27 | displayName: install 28 | inputs: 29 | version: '2.2.300' 30 | 31 | 32 | - task: DotNetCoreCLI@1 33 | displayName: restore 34 | inputs: 35 | command: restore 36 | projects: './src/DeepClone/DeepClone.csproj' 37 | 38 | 39 | - task: DotNetCoreCLI@1 40 | displayName: build 41 | inputs: 42 | command: build 43 | projects: './src/DeepClone/DeepClone.csproj' 44 | arguments: '-c Release' 45 | 46 | 47 | - task: DotNetCoreCLI@2 48 | displayName: test 49 | inputs: 50 | command: test 51 | 52 | 53 | 54 | 55 | - job: macOS 56 | pool: 57 | vmImage: 'macOS-10.14' 58 | 59 | 60 | steps: 61 | - task: DotNetCoreInstaller@0 62 | displayName: install 63 | inputs: 64 | version: '2.2.300' 65 | 66 | 67 | - task: DotNetCoreCLI@1 68 | displayName: restore 69 | inputs: 70 | command: restore 71 | projects: './src/DeepClone/DeepClone.csproj' 72 | 73 | 74 | - task: DotNetCoreCLI@1 75 | displayName: build 76 | inputs: 77 | command: build 78 | projects: './src/DeepClone/DeepClone.csproj' 79 | arguments: '-c Release' 80 | 81 | 82 | - task: DotNetCoreCLI@2 83 | displayName: test 84 | inputs: 85 | command: test 86 | 87 | 88 | 89 | 90 | - job: Windows 91 | pool: 92 | vmImage: 'windows-2019' 93 | 94 | 95 | steps: 96 | - task: DotNetCoreInstaller@0 97 | displayName: install 98 | inputs: 99 | version: '2.2.300' 100 | 101 | 102 | - task: DotNetCoreCLI@1 103 | displayName: restore 104 | inputs: 105 | command: restore 106 | projects: './src/DeepClone/DeepClone.csproj' 107 | 108 | 109 | - task: DotNetCoreCLI@1 110 | displayName: build 111 | inputs: 112 | command: build 113 | projects: './src/DeepClone/DeepClone.csproj' 114 | arguments: '-c Release' 115 | 116 | 117 | - task: DotNetCoreCLI@2 118 | displayName: test 119 | inputs: 120 | command: test -------------------------------------------------------------------------------- /src/DeepClone/Template/List/CloneListTemplate.cs: -------------------------------------------------------------------------------- 1 | using DeepClone.Model; 2 | using Natasha; 3 | using Natasha.CSharp; 4 | using System; 5 | using System.Collections; 6 | using System.Text; 7 | 8 | namespace DeepClone.Template 9 | { 10 | 11 | public class CloneListTemplate : ICloneTemplate 12 | { 13 | 14 | internal readonly static int HashCode; 15 | static CloneListTemplate() => HashCode = typeof(CloneListTemplate).GetHashCode(); 16 | 17 | public override int GetHashCode() => HashCode; 18 | 19 | 20 | 21 | 22 | public bool MatchType(Type type) 23 | { 24 | return type.IsImplementFrom() && !type.IsImplementFrom() && !type.IsArray; 25 | } 26 | 27 | 28 | 29 | 30 | public Delegate TypeRouter(NBuildInfo info) 31 | { 32 | 33 | if (info.CurrentType.IsGenericType) 34 | { 35 | 36 | StringBuilder scriptBuilder = new StringBuilder(); 37 | scriptBuilder.AppendLine(@"if(old!=default){ return "); 38 | 39 | // 初始化目标,区分实体类和接口 40 | if (!info.CurrentType.IsInterface) 41 | { 42 | 43 | scriptBuilder.AppendLine($"new {info.CurrentTypeName}"); 44 | 45 | } 46 | 47 | 48 | scriptBuilder.AppendLine("(old.Select(item=>"); 49 | var parameters = info.CurrentType.GetGenericArguments(); 50 | if (parameters[0].IsSimpleType()) 51 | { 52 | 53 | scriptBuilder.Append("item"); 54 | 55 | } 56 | else if (parameters[0] == typeof(object)) 57 | { 58 | 59 | scriptBuilder.Append("ObjectCloneOperator.Clone(item)"); 60 | 61 | } 62 | else 63 | { 64 | 65 | scriptBuilder.Append("CloneOperator.Clone(item)"); 66 | 67 | } 68 | scriptBuilder.Append("));"); 69 | 70 | 71 | scriptBuilder.AppendLine(@"}return default;"); 72 | var action = FastMethodOperator.UseDomain(info.CurrentType.GetDomain()) 73 | .Using("DeepClone") 74 | .Using("System.Linq") 75 | .Param(info.FatherType, "old") 76 | .Body(scriptBuilder.ToString()) 77 | .Return(info.FatherType) 78 | .Compile(); 79 | return action; 80 | 81 | } 82 | else 83 | { 84 | 85 | throw new Exception("暂不支持"); 86 | 87 | } 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /test/DeepCloneUT/ListTest.cs: -------------------------------------------------------------------------------- 1 | using DeepClone; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using Xunit; 6 | 7 | namespace DeepCloneUT 8 | { 9 | public class TestList 10 | { 11 | public readonly string A; 12 | 13 | public string B; 14 | 15 | public TestList1 List1; 16 | 17 | public TestList2 List2; 18 | } 19 | 20 | public class TestList1 21 | { 22 | private TestList1() { } 23 | 24 | public TestList1(string a) { } 25 | } 26 | 27 | public class TestList2 28 | { 29 | public string A; 30 | 31 | public int B { get; set; } 32 | } 33 | 34 | public class ListTest : Prepare 35 | { 36 | [Fact] 37 | public void ClassCloneTest() 38 | { 39 | 40 | List lli = new List(); 41 | lli.Add("123"); 42 | lli.Add("456"); 43 | var tt1 = CloneOperator.Clone(lli.ToArray()); 44 | Assert.NotSame(lli, tt1); 45 | for (int i = 0; i < lli.Count; i++) 46 | { 47 | Assert.Equal(tt1[i], tt1[i]); 48 | } 49 | 50 | //List kk = new List(); 51 | //TestList t1 = new TestList(); 52 | //t1.B = "13"; 53 | //TestList2 t2 = new TestList2(); 54 | //t2.A = "234"; 55 | //t1.List2 = t2; 56 | //kk.Add(t1); 57 | ////var ttt1 = CloneOperator.Clone(kk); 58 | 59 | //var testList2 = CloneOperator.Clone(t1); // 实体拷贝时出错 60 | 61 | //var tt = CloneOperator.Clone(lli); 62 | //Console.WriteLine(tt == null); 63 | } 64 | 65 | [Fact] 66 | public void ListCloneWithChildClassInstanceTest() 67 | { 68 | var list = new List(){ 69 | new Parent(){A="1",B="1"}, 70 | new Child(){A="1",B=1} 71 | }; 72 | var cloneList = CloneOperator.Clone(list); 73 | Assert.IsType(list[1]); 74 | Assert.IsType(cloneList[1]); 75 | Assert.Equal(list[1].GetType(), cloneList[1].GetType()); 76 | Assert.Equal(((Parent)list[1]).B, ((Parent)cloneList[1]).B); 77 | Assert.Equal(default, cloneList[1].B); 78 | } 79 | 80 | [Fact] 81 | public void CloneWithChildClassInstanceTest() 82 | { 83 | Parent p = new Child() { A = "1", B = 1 }; 84 | Parent cloneP = CloneOperator.Clone(p); 85 | Assert.IsType(p); 86 | Assert.Equal(default, p.B); 87 | Assert.Equal(1, ((Child)p).B); 88 | Assert.IsType(cloneP); 89 | Assert.NotSame(p, cloneP); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /test/DeepCloneUT/ComplexType/Member.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace DeepCloneUT 4 | { 5 | public class Member 6 | { 7 | private long? _id; 8 | 9 | public long Id 10 | { 11 | get 12 | { 13 | if (_id == null) 14 | { 15 | _id = (long)(DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalMilliseconds; 16 | } 17 | return _id.Value; 18 | } 19 | set 20 | { 21 | _id = value; 22 | } 23 | } 24 | 25 | public MemberType MemberType; 26 | 27 | /// 28 | /// 全名 29 | /// 30 | public string FullName => $"{LastName}{(string.IsNullOrEmpty(MiddleName) ? string.Empty : $"-{MiddleName}")}-{FirstName}"; 31 | 32 | /// 33 | /// 名 34 | /// 35 | public string FirstName { get; set; } 36 | 37 | /// 38 | /// 中间名 39 | /// 40 | public string MiddleName { get; set; } 41 | 42 | /// 43 | /// 姓 44 | /// 45 | public string LastName { get; set; } 46 | 47 | /// 48 | /// 年龄 49 | /// 50 | public int Age { get; set; } 51 | 52 | /// 53 | /// 生日 54 | /// 55 | public DateTime? Birthday { get; set; } 56 | 57 | /// 58 | /// 年收入 59 | /// 60 | public decimal AnnualIncome { get; set; } 61 | 62 | /// 63 | /// 老师 64 | /// 65 | public Member Teacher { get; set; } 66 | 67 | public override bool Equals(object obj) 68 | { 69 | var toCompareWith = obj as Member; 70 | if (toCompareWith == null) 71 | return false; 72 | return 73 | this.Id == toCompareWith.Id && 74 | this.MemberType == toCompareWith.MemberType && 75 | this.FullName == toCompareWith.FullName && 76 | this.FirstName == toCompareWith.FirstName && 77 | this.MiddleName == toCompareWith.MiddleName && 78 | this.LastName == toCompareWith.LastName && 79 | this.Age == toCompareWith.Age && 80 | this.Birthday == toCompareWith.Birthday && 81 | this.AnnualIncome == toCompareWith.AnnualIncome && 82 | (this.Teacher == null && toCompareWith.Teacher == null ? true : this.Teacher.Equals(toCompareWith.Teacher)); 83 | } 84 | 85 | public override int GetHashCode() 86 | { 87 | return base.GetHashCode(); 88 | } 89 | 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /src/DeepClone/Template/Dictionary/CloneDictTemplate.cs: -------------------------------------------------------------------------------- 1 | using DeepClone.Model; 2 | using Natasha; 3 | using Natasha.CSharp; 4 | using System; 5 | using System.Collections; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Text; 9 | 10 | namespace DeepClone.Template 11 | { 12 | public class CloneDictTemplate : ICloneTemplate 13 | { 14 | 15 | internal readonly static int HashCode; 16 | static CloneDictTemplate() => HashCode = typeof(CloneDictTemplate).GetHashCode(); 17 | 18 | 19 | 20 | 21 | public override int GetHashCode() => HashCode; 22 | 23 | 24 | 25 | 26 | public bool MatchType(Type type) 27 | { 28 | 29 | return type.IsImplementFrom(); 30 | 31 | } 32 | 33 | 34 | 35 | 36 | public Delegate TypeRouter(NBuildInfo info) 37 | { 38 | 39 | StringBuilder scriptBuilder = new StringBuilder(); 40 | scriptBuilder.AppendLine(@"if(old!=default){ return "); 41 | 42 | 43 | //初始化目标,区分实体类和接口 44 | if (!info.CurrentType.IsInterface) 45 | { 46 | 47 | scriptBuilder.AppendLine($"new {info.CurrentTypeName}"); 48 | 49 | } 50 | scriptBuilder.AppendLine("(old.ToDictionary(item=>KeyValuePair.Create("); 51 | 52 | 53 | 54 | //克隆Key 55 | var keyType = info.CurrentType.GetGenericArguments()[0]; 56 | if (keyType.IsSimpleType()) 57 | { 58 | 59 | scriptBuilder.Append($"item.Key,"); 60 | 61 | } 62 | else if (keyType == typeof(object)) 63 | { 64 | 65 | scriptBuilder.AppendLine($"ObjectCloneOperator.Clone(item.Key),"); 66 | 67 | } 68 | else 69 | { 70 | 71 | scriptBuilder.AppendLine($"CloneOperator.Clone(item.Key),"); 72 | 73 | } 74 | 75 | 76 | //克隆Value 77 | var valueType = info.CurrentType.GetGenericArguments()[1]; 78 | if (valueType.IsSimpleType()) 79 | { 80 | 81 | scriptBuilder.Append($"item.Value"); 82 | 83 | } 84 | else if (keyType == typeof(object)) 85 | { 86 | 87 | scriptBuilder.AppendLine($"ObjectCloneOperator.Clone(item.Value),"); 88 | 89 | } 90 | else 91 | { 92 | 93 | scriptBuilder.AppendLine($"CloneOperator.Clone(item.Value)"); 94 | 95 | } 96 | 97 | 98 | //补全括号,返回默认值。 99 | scriptBuilder.AppendLine(")));}return default;"); 100 | 101 | return FastMethodOperator.UseDomain(info.CurrentType.GetDomain()) 102 | .Using("DeepClone") 103 | .Using("System.Linq") 104 | .Using(typeof(IDictionary)) 105 | .Using(typeof(KeyValuePair<,>)) 106 | .Param(info.FatherType, "old") 107 | .Body(scriptBuilder.ToString()) 108 | .Return(info.FatherType) 109 | .Compile(); 110 | 111 | } 112 | 113 | } 114 | 115 | } 116 | -------------------------------------------------------------------------------- /test/DeepCloneUT/SubclassTest.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using DeepClone; 3 | using Xunit; 4 | 5 | namespace DeepCloneUT 6 | { 7 | public class Parent 8 | { 9 | public string A { get; set; } 10 | public string B { get; set; } 11 | } 12 | 13 | public class Child : Parent 14 | { 15 | public new int B { get; set; } 16 | } 17 | public class Child : Parent 18 | { 19 | public new int B { get; set; } 20 | } 21 | public class Child2 : Parent 22 | { 23 | public new int B { get; set; } 24 | } 25 | 26 | public class Child3 : Child 27 | { 28 | public new string B { get; set; } 29 | } 30 | 31 | public class SupportSubclassTest : Prepare 32 | { 33 | [Fact] 34 | public void ListCloneWithChildClassInstanceTest() 35 | { 36 | var list = new List(){ 37 | new Parent(){A="1",B="1"}, 38 | new Child(){A="1",B=1}, 39 | new Child2(){A="1",B=10}, 40 | new Child2(){A="10",B=100}, 41 | new Child(){A="100",B=1}, 42 | new Child3(){A="1",B="2"} 43 | }; 44 | var cloneList = CloneOperator.Clone(list); 45 | Assert.IsType(cloneList[0]); 46 | Assert.IsType(cloneList[1]); 47 | Assert.IsType(cloneList[2]); 48 | Assert.IsType(cloneList[3]); 49 | Assert.IsType(cloneList[4]); 50 | Assert.IsType(cloneList[5]); 51 | Assert.Equal(list[1].GetType(), cloneList[1].GetType()); 52 | Assert.Equal(((Child)list[1]).B, ((Child)cloneList[1]).B); 53 | Assert.Equal(default, cloneList[1].B); 54 | Assert.Equal(1, ((Child)cloneList[1]).B); 55 | Assert.Equal(10, ((Child2)cloneList[2]).B); 56 | Assert.Equal(100, ((Child2)cloneList[3]).B); 57 | Assert.Equal("10", ((Child2)cloneList[3]).A); 58 | Assert.Equal("100", ((Child)cloneList[4]).A); 59 | Assert.Equal("1", ((Child3)cloneList[5]).A); 60 | Assert.Equal("2", ((Child3)cloneList[5]).B); 61 | } 62 | [Fact] 63 | public void ListCloneWithChildGenericClassInstanceTest() 64 | { 65 | var list = new List(){ 66 | new Parent(){A="1",B="1"}, 67 | new Child(){A="1",B=1}, 68 | }; 69 | var cloneList = CloneOperator.Clone(list); 70 | Assert.IsType>(list[1]); 71 | Assert.IsType>(cloneList[1]); 72 | Assert.Equal(list[1].GetType(), cloneList[1].GetType()); 73 | Assert.Equal(((Parent)list[1]).B, ((Parent)cloneList[1]).B); 74 | Assert.Equal(default, cloneList[1].B); 75 | Assert.Equal(1, ((Child)cloneList[1]).B); 76 | } 77 | 78 | [Fact] 79 | 80 | public void CloneWithChildClassInstanceTest() 81 | { 82 | Parent p = new Child() { A = "1", B = 1 }; 83 | Parent cloneP = CloneOperator.Clone(p); 84 | Assert.IsType(p); 85 | Assert.Equal(default, p.B); 86 | Assert.Equal(1, ((Child)cloneP).B); 87 | Assert.IsType(cloneP); 88 | Assert.NotSame(p, cloneP); 89 | } 90 | 91 | } 92 | } -------------------------------------------------------------------------------- /DeepClone.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29102.190 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DeepClone", "src\DeepClone\DeepClone.csproj", "{A694DE82-5789-4D66-9A8E-BD67732323CE}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DeepCloneUT", "test\DeepCloneUT\DeepCloneUT.csproj", "{D0B8CDD9-979F-4E8C-837E-50DF8313E680}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{99EFD576-BA76-4837-A8DC-A0A3078A0183}" 11 | EndProject 12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{202285D9-831D-444A-94EF-984879E56A54}" 13 | EndProject 14 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{00F21AB9-2F67-4A16-BFD0-6A5C984595E9}" 15 | EndProject 16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DeepCloneSelfTest", "samples\DeepCloneSelfTest\DeepCloneSelfTest.csproj", "{8042241C-9C8B-465B-94DC-CB9E5CDA1500}" 17 | EndProject 18 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{BBA88067-F69B-44C3-910F-C28F7B14A921}" 19 | ProjectSection(SolutionItems) = preProject 20 | .gitignore = .gitignore 21 | .travis.yml = .travis.yml 22 | _config.yml = _config.yml 23 | appveyor.yml = appveyor.yml 24 | azure-pipelines.yml = azure-pipelines.yml 25 | Directory.Build.props = Directory.Build.props 26 | README.md = README.md 27 | EndProjectSection 28 | EndProject 29 | Global 30 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 31 | Debug|Any CPU = Debug|Any CPU 32 | Release|Any CPU = Release|Any CPU 33 | EndGlobalSection 34 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 35 | {A694DE82-5789-4D66-9A8E-BD67732323CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 36 | {A694DE82-5789-4D66-9A8E-BD67732323CE}.Debug|Any CPU.Build.0 = Debug|Any CPU 37 | {A694DE82-5789-4D66-9A8E-BD67732323CE}.Release|Any CPU.ActiveCfg = Release|Any CPU 38 | {A694DE82-5789-4D66-9A8E-BD67732323CE}.Release|Any CPU.Build.0 = Release|Any CPU 39 | {D0B8CDD9-979F-4E8C-837E-50DF8313E680}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 40 | {D0B8CDD9-979F-4E8C-837E-50DF8313E680}.Debug|Any CPU.Build.0 = Debug|Any CPU 41 | {D0B8CDD9-979F-4E8C-837E-50DF8313E680}.Release|Any CPU.ActiveCfg = Release|Any CPU 42 | {D0B8CDD9-979F-4E8C-837E-50DF8313E680}.Release|Any CPU.Build.0 = Release|Any CPU 43 | {8042241C-9C8B-465B-94DC-CB9E5CDA1500}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 44 | {8042241C-9C8B-465B-94DC-CB9E5CDA1500}.Debug|Any CPU.Build.0 = Debug|Any CPU 45 | {8042241C-9C8B-465B-94DC-CB9E5CDA1500}.Release|Any CPU.ActiveCfg = Release|Any CPU 46 | {8042241C-9C8B-465B-94DC-CB9E5CDA1500}.Release|Any CPU.Build.0 = Release|Any CPU 47 | EndGlobalSection 48 | GlobalSection(SolutionProperties) = preSolution 49 | HideSolutionNode = FALSE 50 | EndGlobalSection 51 | GlobalSection(NestedProjects) = preSolution 52 | {A694DE82-5789-4D66-9A8E-BD67732323CE} = {99EFD576-BA76-4837-A8DC-A0A3078A0183} 53 | {D0B8CDD9-979F-4E8C-837E-50DF8313E680} = {202285D9-831D-444A-94EF-984879E56A54} 54 | {8042241C-9C8B-465B-94DC-CB9E5CDA1500} = {00F21AB9-2F67-4A16-BFD0-6A5C984595E9} 55 | EndGlobalSection 56 | GlobalSection(ExtensibilityGlobals) = postSolution 57 | SolutionGuid = {2A5D981C-730C-4FCB-AFBB-B84033E60ED3} 58 | EndGlobalSection 59 | EndGlobal 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |

3 | 中文 | 4 | English 5 |

6 | 7 | # DeepClone 8 | 9 | [![Member project of Night Moon Studio](https://img.shields.io/badge/member%20project%20of-NMS-9e20c9.svg)](https://github.com/night-moon-studio) 10 | [![NuGet Badge](https://buildstats.info/nuget/DotNetCore.Natasha.deepclone?includePreReleases=true)](https://www.nuget.org/packages/DotNetCore.Natasha.deepclone) 11 | ![GitHub repo size](https://img.shields.io/github/repo-size/night-moon-studio/deepclone.svg) 12 | [![Gitter](https://badges.gitter.im/NightMoonStudio/DeepClone.svg)](https://gitter.im/NightMoonStudio/DeepClone?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) 13 | [![Codecov](https://img.shields.io/codecov/c/github/night-moon-studio/deepclone.svg)](https://codecov.io/gh/night-moon-studio/deepclone) 14 | [![Badge](https://img.shields.io/badge/link-996.icu-red.svg)](https://996.icu/#/zh_CN) 15 | [![GitHub license](https://img.shields.io/github/license/night-moon-studio/deepclone.svg)](https://github.com/night-moon-studio/deepclone/blob/master/LICENSE) 16 | 17 | 18 |
19 | 20 | 21 | ### 持续构建(CI Build Status) 22 | 23 | | CI Platform | Build Server | Master Build | Master Test | 24 | |--------- |------------- |---------| --------| 25 | | Travis | Linux/OSX | [![Build status](https://travis-ci.org/night-moon-studio/deepclone.svg?branch=master)](https://travis-ci.org/night-moon-studio/deepclone) | | 26 | | AppVeyor | Windows/Linux |[![Build status](https://ci.appveyor.com/api/projects/status/4qwm7p9dpy7agdoa?svg=true)](https://ci.appveyor.com/project/NMSAzulX/deepclone)|[![Build status](https://img.shields.io/appveyor/tests/NMSAzulX/deepclone.svg)](https://ci.appveyor.com/project/NMSAzulX/deepclone)| 27 | | Azure | Windows |[![Build Status](https://dev.azure.com/NightMoonStudio/DeepClone/_apis/build/status/night-moon-studio.DeepClone?branchName=master&jobName=Windows)](https://dev.azure.com/NightMoonStudio/DeepClone/_build/latest?definitionId=5&branchName=master)|[![Azure DevOps tests](https://img.shields.io/azure-devops/tests/NightMoonStudio/DeepClone/4.svg)](https://dev.azure.com/NightMoonStudio/DeepClone/_build/latest?definitionId=5&branchName=master) | 28 | | Azure | Linux |[![Build Status](https://dev.azure.com/NightMoonStudio/DeepClone/_apis/build/status/night-moon-studio.DeepClone?branchName=master&jobName=Linux)](https://dev.azure.com/NightMoonStudio/DeepClone/_build/latest?definitionId=5&branchName=master)|[![Azure DevOps tests](https://img.shields.io/azure-devops/tests/NightMoonStudio/DeepClone/4.svg)](https://dev.azure.com/NightMoonStudio/DeepClone/_build/latest?definitionId=5&branchName=master) | 29 | | Azure | Mac |[![Build Status](https://dev.azure.com/NightMoonStudio/DeepClone/_apis/build/status/night-moon-studio.DeepClone?branchName=master&jobName=macOS)](https://dev.azure.com/NightMoonStudio/DeepClone/_build/latest?definitionId=5&branchName=master)|[![Azure DevOps tests](https://img.shields.io/azure-devops/tests/NightMoonStudio/DeepClone/4.svg)](https://dev.azure.com/NightMoonStudio/DeepClone/_build/latest?definitionId=5&branchName=master) | 30 | 31 |
32 | 33 | ### 项目简介: 34 | 35 | 此项目为[Natasha](https://github.com/dotnetcore/Natasha)的衍生项目,为用户提供高性能的深度克隆。 36 | 37 |
38 | 39 | 40 | #### 使用方法(User Api): 41 | 42 |
43 | 44 | - 引入 动态构件库: NMS.DeepClone 45 | 46 | - 初始化: NatashaInitializer.InitializeAndPreheating(); 47 | 48 | 49 | - 敲代码 50 | 51 | 52 |
53 | 54 | 55 | ```C# 56 | 57 | //非object类型使用 58 | CloneOperator.Clone(instance); 59 | 60 | //object类型使用 61 | ObjectCloneOperator.Clone(obj); 62 | 63 | ``` 64 | 65 | ```C# 66 | //readonly 字段会根据构造函数中参数名,或者通过注解进行匹配 67 | 68 | public class A() 69 | { 70 | 71 | public A(string name,int age){ StuName = name; Age = age; } 72 | 73 | [NeedCtor("name")] 74 | public readonly StuName; 75 | 76 | [NeedCtor] 77 | public readonly Age; 78 | 79 | } 80 | ``` 81 | 82 | ### 发布计划: 83 | 84 | - 2019-08-20 : 发布v1.0.0.0, 高性能动态深度克隆库。 85 | 86 |
87 | 88 | --------------------- 89 |
90 | 91 | 92 | 93 | ## License 94 | [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fnight-moon-studio%2Fdeepclone.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fnight-moon-studio%2Fdeepclone?ref=badge_large) 95 | -------------------------------------------------------------------------------- /src/DeepClone/Template/Class/CtorTempalte.cs: -------------------------------------------------------------------------------- 1 | using Natasha; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace DeepClone.Template 7 | { 8 | public class CtorTempalte 9 | { 10 | 11 | 12 | private readonly HashSet> _ctors; 13 | private readonly Dictionary, Dictionary> _parametersMapping; 14 | //private Dictionary, int> _values; 15 | private readonly string _instanceName; 16 | 17 | public CtorTempalte(Type type,string instanceName) 18 | { 19 | 20 | _instanceName = instanceName; 21 | _ctors = new HashSet>(); 22 | //_values = new Dictionary, int>(); 23 | _parametersMapping = new Dictionary, Dictionary>(); 24 | var temp = type.GetConstructors(); 25 | 26 | 27 | //获取所有构造函数 28 | for (int i = 0; i < temp.Length; i++) 29 | { 30 | 31 | var dict = new Dictionary(); 32 | _ctors.Add(dict); 33 | 34 | 35 | //匹配频次字典 36 | //_values[dict] = 0; 37 | 38 | 39 | //初始化构造字典 40 | _parametersMapping[dict] = new Dictionary(); 41 | var parameters = temp[i].GetParameters(); 42 | foreach (var item in parameters) 43 | { 44 | 45 | //缓存构造信息 46 | dict[item.Name.ToUpper()] = item.ParameterType; 47 | _parametersMapping[dict][item.Name.ToUpper()] = item.Name; 48 | 49 | } 50 | 51 | } 52 | 53 | } 54 | 55 | 56 | 57 | 58 | public string GetCtor(IEnumerable infos) 59 | { 60 | 61 | Dictionary pairs = default; 62 | int preResult = 0; 63 | foreach (var item in _ctors) 64 | { 65 | 66 | int value = 0; 67 | 68 | //遍历符合条件的成员 69 | foreach (var info in infos) 70 | { 71 | 72 | //从参数缓存中查找是否存在该类型 73 | var name = info.MemberTypeAvailableName; 74 | if (item.ContainsKey(name)) 75 | { 76 | 77 | if (item[name] == info.MemberType) 78 | { 79 | //频次+1 80 | value += 1; 81 | } 82 | 83 | } 84 | 85 | } 86 | 87 | 88 | if (preResult < value) 89 | { 90 | //选择最大的匹配节点 91 | preResult = value; 92 | pairs = item; 93 | } 94 | 95 | 96 | } 97 | 98 | if (pairs != default) 99 | { 100 | 101 | //通过最高频次的匹配字典找到参数真名缓存 102 | var cache = _parametersMapping[pairs]; 103 | 104 | 105 | //生成脚本 106 | StringBuilder scriptBuilder = new StringBuilder(); 107 | foreach (var item in infos) 108 | { 109 | 110 | var name = item.MemberTypeAvailableName; 111 | if (cache.ContainsKey(name)) 112 | { 113 | 114 | //如果名字和类型都匹配上了 115 | if (pairs[name] == item.MemberType) 116 | { 117 | scriptBuilder.Append($"{cache[name]}:{_instanceName}.{item.MemberName},"); 118 | pairs.Remove(name); 119 | } 120 | 121 | } 122 | 123 | 124 | } 125 | 126 | 127 | //没匹配上的都传default 128 | foreach (var item in pairs) 129 | { 130 | scriptBuilder.Append($"{cache[item.Key]}:default,"); 131 | } 132 | 133 | 134 | if (scriptBuilder.Length>0) 135 | { 136 | scriptBuilder.Length -= 1; 137 | } 138 | return scriptBuilder.ToString(); 139 | 140 | } 141 | 142 | return default; 143 | 144 | } 145 | 146 | } 147 | 148 | } 149 | -------------------------------------------------------------------------------- /test/DeepCloneUT/Model/CloneTestModel.cs: -------------------------------------------------------------------------------- 1 | using DeepClone; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace NatashaUT.Model 6 | { 7 | public class FieldCloneNormalModel 8 | { 9 | public const int Const=100; 10 | public readonly int ReadOnly; 11 | public CloneEnum Flag; 12 | public int Age; 13 | public string Name; 14 | public bool Title; 15 | public DateTime Timer; 16 | public decimal money; 17 | public long Id; 18 | } 19 | 20 | public enum CloneEnum 21 | { 22 | A,B,C 23 | } 24 | 25 | public class FieldCloneArrayModel 26 | { 27 | 28 | public string[] Name; 29 | } 30 | 31 | public class FieldCloneClassArrayModel 32 | { 33 | 34 | public FieldCloneNormalModel[] Models; 35 | } 36 | 37 | public class FieldCloneSubNodeModel 38 | { 39 | 40 | public FieldCloneNormalModel Node; 41 | } 42 | 43 | public class FieldCloneClassCollectionModel 44 | { 45 | 46 | public List Nodes; 47 | } 48 | 49 | 50 | public class PropCloneNormalModel 51 | { 52 | public readonly bool NoUseCtor; 53 | public PropCloneNormalModel() 54 | { 55 | NoUseCtor = true; 56 | ReadOnly = 123; 57 | ReadOnlyString = "2323"; 58 | ReadOnlyString1 = "3232"; 59 | } 60 | 61 | 62 | 63 | public PropCloneNormalModel(string Readonly, string abc, string readonlyString1, string asds) 64 | { 65 | 66 | ReadOnlyString = abc; 67 | ReadOnlyString1 = readonlyString1; 68 | 69 | } 70 | 71 | public PropCloneNormalModel(string Readonly, string abc, string asds) 72 | { 73 | 74 | ReadOnlyString = abc; 75 | 76 | } 77 | public PropCloneNormalModel(int Readonly) 78 | { 79 | 80 | ReadOnly = Readonly; 81 | 82 | } 83 | public const int Const = 100; 84 | [NeedCtor] 85 | public readonly int ReadOnly; 86 | [NeedCtor("abc")] 87 | public readonly string ReadOnlyString; 88 | [NeedCtor] 89 | public readonly string ReadOnlyString1; 90 | public int Age; 91 | public string Name; 92 | public bool Title; 93 | public DateTime Timer; 94 | public decimal money; 95 | public long Id; 96 | } 97 | 98 | public class PropCloneArrayModel 99 | { 100 | 101 | public string[] Name { get; set; } 102 | } 103 | 104 | public class PropCloneClassArrayModel 105 | { 106 | 107 | public PropCloneNormalModel[] Models { get; set; } 108 | } 109 | 110 | public class PropCloneSubNodeModel 111 | { 112 | 113 | public PropCloneNormalModel Node { get; set; } 114 | } 115 | public class PropCloneClassCollectionModel 116 | { 117 | public List Nodes { get; set; } 118 | } 119 | public class CloneCollectionModel 120 | { 121 | public List[] ALNodes { get; set; } 122 | public List LANodes { get; set; } 123 | public IEnumerable> LLNodes { get; set; } 124 | } 125 | 126 | public class CloneDictModel 127 | { 128 | public Dictionary Dicts; 129 | } 130 | public class CloneDictCollectionModel 131 | { 132 | public Dictionary, List> Dicts; 133 | } 134 | public class CloneDictArrayModel 135 | { 136 | public IDictionary[] Dicts; 137 | } 138 | 139 | public class FieldLinkModel 140 | { 141 | public LinkedList Nodes; 142 | public string Name; 143 | public int Age; 144 | public FieldLinkModel() 145 | { 146 | Nodes = new LinkedList(); 147 | } 148 | } 149 | 150 | public class FieldLinkArrayModel 151 | { 152 | public LinkedList[] Nodes; 153 | public string Name; 154 | public int Age; 155 | } 156 | 157 | public class FieldSelfLinkModel 158 | { 159 | public FieldSelfLinkModel Next; 160 | public string Name; 161 | public int Age; 162 | } 163 | 164 | public class FieldSelfLinkArrayModel 165 | { 166 | public FieldSelfLinkArrayModel[] Next; 167 | public string Name; 168 | public int Age; 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /test/DeepCloneUT/ClassTest.cs: -------------------------------------------------------------------------------- 1 | using DeepClone; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using Xunit; 6 | 7 | namespace DeepCloneUT 8 | { 9 | 10 | 11 | public class TestModel 12 | { 13 | public readonly string A; 14 | 15 | public string B; 16 | 17 | public TestModel1 Model1; 18 | 19 | public TestModel2 Model2; 20 | 21 | public TestModel Self; 22 | 23 | public List SelfList; 24 | 25 | 26 | public class InnerClass 27 | { 28 | public string Name; 29 | public List SelfList; 30 | } 31 | } 32 | 33 | public class TestModel1 34 | { 35 | public TestModel1() { } 36 | 37 | public TestModel1(string a) { } 38 | } 39 | 40 | public class TestModel2 41 | { 42 | public string A; 43 | 44 | public int B { get; set; } 45 | } 46 | 47 | public class ClassTest : Prepare 48 | { 49 | [Fact] 50 | public void ClassCloneTest() 51 | { 52 | 53 | var model = new TestModel 54 | { 55 | B = "B", 56 | Model1 = new TestModel1("Model1"), 57 | Model2=new TestModel2 { A="A",B=1}, 58 | Self = new TestModel { B="E"}, 59 | SelfList = new List { new TestModel { B = "l" } } 60 | 61 | }; 62 | var testModel=CloneOperator.Clone(model); 63 | Assert.NotSame(model, testModel); 64 | Assert.Equal(model.B, testModel.B); 65 | Assert.NotNull(model.Model1); 66 | Assert.NotSame(model.Model2, testModel.Model2); 67 | Assert.Equal(model.Model2.A, testModel.Model2.A); 68 | Assert.Equal(model.Model2.B, testModel.Model2.B); 69 | Assert.NotSame(model.Self, testModel.Self); 70 | Assert.Equal(model.Self.B, testModel.Self.B); 71 | Assert.NotSame(model.SelfList, testModel.SelfList); 72 | Assert.Equal(model.SelfList[0].B, testModel.SelfList[0].B); 73 | } 74 | 75 | [Fact] 76 | public void ClassInnerTest() 77 | { 78 | TestModel.InnerClass model = new TestModel.InnerClass(); 79 | model.Name = "abc"; 80 | model.SelfList = new List { new TestModel { B = "l" } }; 81 | var testModel = CloneOperator.Clone(model); 82 | Assert.NotSame(model, testModel); 83 | Assert.NotSame(model.SelfList, testModel.SelfList); 84 | Assert.Equal(model.SelfList[0].B, testModel.SelfList[0].B); 85 | } 86 | 87 | 88 | [Fact] 89 | public void ObjectCloneTest() 90 | { 91 | 92 | var model = new TestModel 93 | { 94 | B = "B", 95 | Model1 = new TestModel1("Model1"), 96 | Model2 = new TestModel2 { A = "A", B = 1 }, 97 | Self = new TestModel { B = "E" }, 98 | SelfList = new List { new TestModel { B = "l" } } 99 | 100 | }; 101 | object obj = model; 102 | var testModel = ObjectCloneOperator.Clone(obj); 103 | Assert.NotSame(model, testModel); 104 | Assert.Equal(model.B, ((TestModel)testModel).B); 105 | Assert.NotNull(model.Model1); 106 | Assert.NotSame(model.Model2, ((TestModel)testModel).Model2); 107 | Assert.Equal(model.Model2.A, ((TestModel)testModel).Model2.A); 108 | Assert.Equal(model.Model2.B, ((TestModel)testModel).Model2.B); 109 | Assert.NotSame(model.Self, ((TestModel)testModel).Self); 110 | Assert.Equal(model.Self.B, ((TestModel)testModel).Self.B); 111 | Assert.NotSame(model.SelfList, ((TestModel)testModel).SelfList); 112 | Assert.Equal(model.SelfList[0].B, ((TestModel)testModel).SelfList[0].B); 113 | } 114 | 115 | [Fact] 116 | public void ObjectSourceTest() 117 | { 118 | object obj = new object(); 119 | var testModel = ObjectCloneOperator.Clone(obj); 120 | Assert.NotNull(testModel); 121 | Assert.NotSame(obj, testModel); 122 | } 123 | 124 | [Fact] 125 | public void ObjectInnerTest() 126 | { 127 | TestModel.InnerClass model = new TestModel.InnerClass(); 128 | model.Name = "abc"; 129 | model.SelfList = new List { new TestModel { B = "l" } }; 130 | object obj = model; 131 | var testModel = ObjectCloneOperator.Clone(obj); 132 | Assert.NotSame(obj, testModel); 133 | Assert.NotSame(model.SelfList, ((TestModel.InnerClass)testModel).SelfList); 134 | Assert.Equal(model.SelfList[0].B, ((TestModel.InnerClass)testModel).SelfList[0].B); 135 | } 136 | } 137 | 138 | 139 | } 140 | -------------------------------------------------------------------------------- /src/DeepClone/Template/Class/CloneClassTemplate.cs: -------------------------------------------------------------------------------- 1 | using DeepClone.Model; 2 | using Natasha; 3 | using Natasha.CSharp; 4 | using System; 5 | using System.Collections; 6 | using System.Collections.Generic; 7 | using System.Reflection; 8 | using System.Text; 9 | 10 | namespace DeepClone.Template 11 | { 12 | public class CloneClassTemplate : ICloneTemplate 13 | { 14 | 15 | internal readonly static int HashCode; 16 | private CtorTempalte CtorHandler; 17 | static CloneClassTemplate() => HashCode = typeof(CloneClassTemplate).GetHashCode(); 18 | 19 | 20 | 21 | 22 | public override int GetHashCode() => HashCode; 23 | 24 | 25 | 26 | 27 | public bool MatchType(Type type) 28 | { 29 | return type.IsClass 30 | && type != typeof(object) 31 | && !type.IsImplementFrom() 32 | && !type.IsSimpleType() 33 | && !type.IsArray 34 | && !type.IsInterface; 35 | } 36 | 37 | public Delegate TypeRouter(NBuildInfo info) 38 | { 39 | 40 | var instanceName = "oldSource"; 41 | info.FatherType = info.FatherType == typeof(object) ? info.CurrentType : info.FatherType; 42 | if (info.CurrentType != info.FatherType) 43 | { 44 | instanceName = "old"; 45 | } 46 | CtorHandler = new CtorTempalte(info.CurrentType, instanceName); 47 | 48 | 49 | StringBuilder scriptBuilder = new StringBuilder(); 50 | var memberBuilder = new StringBuilder(); 51 | var builder = FastMethodOperator.UseDomain(info.CurrentType.GetDomain()); 52 | //构造函数处理: 不存在public无参构造函数无法克隆; 53 | if (info.CurrentType.GetConstructor(new Type[0]) == null) 54 | { 55 | return default; 56 | } 57 | 58 | 59 | var members = NBuildInfo.GetInfos(info.CurrentType); 60 | foreach (var item in members) 61 | { 62 | 63 | var member = item.Value; 64 | if (member == null) 65 | { 66 | continue; 67 | } 68 | 69 | 70 | if (member.CanWrite && member.CanRead && !member.IsStatic) 71 | { 72 | if (member.MemberType.IsSimpleType()) 73 | { 74 | 75 | //简单类型直接赋值(值类型) 76 | memberBuilder.Append($"{member.MemberName}={instanceName}.{member.MemberName},"); 77 | 78 | } 79 | else if (member.MemberType == typeof(object)) 80 | { 81 | 82 | //如果是object类型,那么使用object克隆方法 83 | memberBuilder.Append($"{member.MemberName}=ObjectCloneOperator.Clone({instanceName}.{member.MemberName}),"); 84 | 85 | } 86 | else 87 | { 88 | 89 | //如果是运行时类型 90 | //精确添加Using防止二义性引用 91 | builder.Using(member.MemberType); 92 | memberBuilder.Append($"{member.MemberName}=CloneOperator.Clone({instanceName}.{member.MemberName}),"); 93 | 94 | } 95 | 96 | } 97 | } 98 | 99 | 100 | List infos = new List(); 101 | foreach (var fieldInfo in info.CurrentType.GetFields(BindingFlags.Instance | BindingFlags.Public)) 102 | { 103 | 104 | //获取NeetCtor注解 105 | var ctorAttr = fieldInfo.GetCustomAttribute(); 106 | if (ctorAttr != default) 107 | { 108 | NBuildInfo tempInfo = fieldInfo; 109 | tempInfo.MemberTypeAvailableName = ctorAttr.Name == default ? fieldInfo.Name.ToUpper() : ctorAttr.Name.ToUpper(); 110 | infos.Add(tempInfo); 111 | } 112 | 113 | } 114 | 115 | foreach (var propertyInfo in info.CurrentType.GetProperties(BindingFlags.Instance | BindingFlags.Public)) 116 | { 117 | 118 | //获取NeetCtor注解 119 | var ctorAttr = propertyInfo.GetCustomAttribute(); 120 | if (ctorAttr != default) 121 | { 122 | NBuildInfo tempInfo = propertyInfo; 123 | tempInfo.MemberTypeAvailableName = ctorAttr.Name == default ? propertyInfo.Name.ToUpper() : ctorAttr.Name.ToUpper(); 124 | infos.Add(tempInfo); 125 | } 126 | 127 | } 128 | 129 | string readonlyScript = CtorHandler.GetCtor(infos); 130 | if (info.CurrentType == info.FatherType) 131 | { 132 | scriptBuilder.Insert(0, $"if(oldSource!=default){{ return new {info.CurrentTypeName}({readonlyScript}) {{"); 133 | } 134 | else 135 | { 136 | scriptBuilder.Insert(0, $"if(oldSource!=default){{ var old = ({info.CurrentTypeName})oldSource; return new {info.CurrentTypeName}({readonlyScript}) {{"); 137 | } 138 | 139 | 140 | if (memberBuilder.Length > 0) 141 | { 142 | memberBuilder.Length -= 1; 143 | scriptBuilder.Append(memberBuilder); 144 | } 145 | 146 | 147 | scriptBuilder.Append("};}return default;"); 148 | return builder 149 | .Using("DeepClone") 150 | .Using(info.CurrentType) 151 | .Param(info.FatherType, "oldSource") 152 | .Body(scriptBuilder.ToString()) 153 | .Return(info.FatherType) 154 | .Compile(); 155 | } 156 | 157 | } 158 | 159 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | .vscode/ 7 | 8 | # User-specific files 9 | *.suo 10 | *.user 11 | *.userosscache 12 | *.sln.docstates 13 | 14 | # User-specific files (MonoDevelop/Xamarin Studio) 15 | *.userprefs 16 | 17 | # Build results 18 | [Dd]ebug/ 19 | [Dd]ebugPublic/ 20 | [Rr]elease/ 21 | [Rr]eleases/ 22 | x64/ 23 | x86/ 24 | bld/ 25 | [Bb]in/ 26 | [Oo]bj/ 27 | [Ll]og/ 28 | 29 | # Visual Studio 2015/2017 cache/options directory 30 | .vs/ 31 | # Uncomment if you have tasks that create the project's static files in wwwroot 32 | #wwwroot/ 33 | 34 | # Visual Studio 2017 auto generated files 35 | Generated\ Files/ 36 | 37 | # MSTest test Results 38 | [Tt]est[Rr]esult*/ 39 | [Bb]uild[Ll]og.* 40 | 41 | # NUNIT 42 | *.VisualState.xml 43 | TestResult.xml 44 | 45 | # Build Results of an ATL Project 46 | [Dd]ebugPS/ 47 | [Rr]eleasePS/ 48 | dlldata.c 49 | 50 | # Benchmark Results 51 | BenchmarkDotNet.Artifacts/ 52 | 53 | # .NET Core 54 | project.lock.json 55 | project.fragment.lock.json 56 | artifacts/ 57 | **/Properties/launchSettings.json 58 | 59 | # StyleCop 60 | StyleCopReport.xml 61 | 62 | # Files built by Visual Studio 63 | *_i.c 64 | *_p.c 65 | *_i.h 66 | *.ilk 67 | *.meta 68 | *.obj 69 | *.iobj 70 | *.pch 71 | *.pdb 72 | *.ipdb 73 | *.pgc 74 | *.pgd 75 | *.rsp 76 | *.sbr 77 | *.tlb 78 | *.tli 79 | *.tlh 80 | *.tmp 81 | *.tmp_proj 82 | *.log 83 | *.vspscc 84 | *.vssscc 85 | .builds 86 | *.pidb 87 | *.svclog 88 | *.scc 89 | 90 | # Chutzpah Test files 91 | _Chutzpah* 92 | 93 | # Visual C++ cache files 94 | ipch/ 95 | *.aps 96 | *.ncb 97 | *.opendb 98 | *.opensdf 99 | *.sdf 100 | *.cachefile 101 | *.VC.db 102 | *.VC.VC.opendb 103 | 104 | # Visual Studio profiler 105 | *.psess 106 | *.vsp 107 | *.vspx 108 | *.sap 109 | 110 | # Visual Studio Trace Files 111 | *.e2e 112 | 113 | # TFS 2012 Local Workspace 114 | $tf/ 115 | 116 | # Guidance Automation Toolkit 117 | *.gpState 118 | 119 | # ReSharper is a .NET coding add-in 120 | _ReSharper*/ 121 | *.[Rr]e[Ss]harper 122 | *.DotSettings.user 123 | 124 | # JustCode is a .NET coding add-in 125 | .JustCode 126 | 127 | # TeamCity is a build add-in 128 | _TeamCity* 129 | 130 | # DotCover is a Code Coverage Tool 131 | *.dotCover 132 | 133 | # AxoCover is a Code Coverage Tool 134 | .axoCover/* 135 | !.axoCover/settings.json 136 | 137 | # Visual Studio code coverage results 138 | *.coverage 139 | *.coveragexml 140 | 141 | # NCrunch 142 | _NCrunch_* 143 | .*crunch*.local.xml 144 | nCrunchTemp_* 145 | 146 | # MightyMoose 147 | *.mm.* 148 | AutoTest.Net/ 149 | 150 | # Web workbench (sass) 151 | .sass-cache/ 152 | 153 | # Installshield output folder 154 | [Ee]xpress/ 155 | 156 | # DocProject is a documentation generator add-in 157 | DocProject/buildhelp/ 158 | DocProject/Help/*.HxT 159 | DocProject/Help/*.HxC 160 | DocProject/Help/*.hhc 161 | DocProject/Help/*.hhk 162 | DocProject/Help/*.hhp 163 | DocProject/Help/Html2 164 | DocProject/Help/html 165 | 166 | # Click-Once directory 167 | publish/ 168 | 169 | # Publish Web Output 170 | *.[Pp]ublish.xml 171 | *.azurePubxml 172 | # Note: Comment the next line if you want to checkin your web deploy settings, 173 | # but database connection strings (with potential passwords) will be unencrypted 174 | *.pubxml 175 | *.publishproj 176 | 177 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 178 | # checkin your Azure Web App publish settings, but sensitive information contained 179 | # in these scripts will be unencrypted 180 | PublishScripts/ 181 | 182 | # NuGet Packages 183 | *.nupkg 184 | # The packages folder can be ignored because of Package Restore 185 | **/[Pp]ackages/* 186 | # except build/, which is used as an MSBuild target. 187 | !**/[Pp]ackages/build/ 188 | # Uncomment if necessary however generally it will be regenerated when needed 189 | #!**/[Pp]ackages/repositories.config 190 | # NuGet v3's project.json files produces more ignorable files 191 | *.nuget.props 192 | *.nuget.targets 193 | 194 | # Microsoft Azure Build Output 195 | csx/ 196 | *.build.csdef 197 | 198 | # Microsoft Azure Emulator 199 | ecf/ 200 | rcf/ 201 | 202 | # Windows Store app package directories and files 203 | AppPackages/ 204 | BundleArtifacts/ 205 | Package.StoreAssociation.xml 206 | _pkginfo.txt 207 | *.appx 208 | 209 | # Visual Studio cache files 210 | # files ending in .cache can be ignored 211 | *.[Cc]ache 212 | # but keep track of directories ending in .cache 213 | !*.[Cc]ache/ 214 | 215 | # Others 216 | ClientBin/ 217 | ~$* 218 | *~ 219 | *.dbmdl 220 | *.dbproj.schemaview 221 | *.jfm 222 | *.pfx 223 | *.publishsettings 224 | orleans.codegen.cs 225 | 226 | # Including strong name files can present a security risk 227 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 228 | #*.snk 229 | 230 | # Since there are multiple workflows, uncomment next line to ignore bower_components 231 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 232 | #bower_components/ 233 | 234 | # RIA/Silverlight projects 235 | Generated_Code/ 236 | 237 | # Backup & report files from converting an old project file 238 | # to a newer Visual Studio version. Backup files are not needed, 239 | # because we have git ;-) 240 | _UpgradeReport_Files/ 241 | Backup*/ 242 | UpgradeLog*.XML 243 | UpgradeLog*.htm 244 | ServiceFabricBackup/ 245 | *.rptproj.bak 246 | 247 | # SQL Server files 248 | *.mdf 249 | *.ldf 250 | *.ndf 251 | 252 | # Business Intelligence projects 253 | *.rdl.data 254 | *.bim.layout 255 | *.bim_*.settings 256 | *.rptproj.rsuser 257 | 258 | # Microsoft Fakes 259 | FakesAssemblies/ 260 | 261 | # GhostDoc plugin setting file 262 | *.GhostDoc.xml 263 | 264 | # Node.js Tools for Visual Studio 265 | .ntvs_analysis.dat 266 | node_modules/ 267 | 268 | # Visual Studio 6 build log 269 | *.plg 270 | 271 | # Visual Studio 6 workspace options file 272 | *.opt 273 | 274 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 275 | *.vbw 276 | 277 | # Visual Studio LightSwitch build output 278 | **/*.HTMLClient/GeneratedArtifacts 279 | **/*.DesktopClient/GeneratedArtifacts 280 | **/*.DesktopClient/ModelManifest.xml 281 | **/*.Server/GeneratedArtifacts 282 | **/*.Server/ModelManifest.xml 283 | _Pvt_Extensions 284 | 285 | # Paket dependency manager 286 | .paket/paket.exe 287 | paket-files/ 288 | 289 | # FAKE - F# Make 290 | .fake/ 291 | 292 | # JetBrains Rider 293 | .idea/ 294 | *.sln.iml 295 | 296 | # CodeRush 297 | .cr/ 298 | 299 | # Python Tools for Visual Studio (PTVS) 300 | __pycache__/ 301 | *.pyc 302 | 303 | # Cake - Uncomment if you are using it 304 | # tools/** 305 | # !tools/packages.config 306 | 307 | # Tabs Studio 308 | *.tss 309 | 310 | # Telerik's JustMock configuration file 311 | *.jmconfig 312 | 313 | # BizTalk build output 314 | *.btp.cs 315 | *.btm.cs 316 | *.odx.cs 317 | *.xsd.cs 318 | 319 | # OpenCover UI analysis results 320 | OpenCover/ 321 | 322 | # Azure Stream Analytics local run output 323 | ASALocalRun/ 324 | 325 | # MSBuild Binary and Structured Log 326 | *.binlog 327 | 328 | # NVidia Nsight GPU debugger configuration file 329 | *.nvuser 330 | 331 | # MFractors (Xamarin productivity tool) working folder 332 | .mfractor/ 333 | -------------------------------------------------------------------------------- /src/DeepClone/Template/Array/CloneArrayTemplate.cs: -------------------------------------------------------------------------------- 1 | using DeepClone.Model; 2 | using Natasha; 3 | using Natasha.CSharp; 4 | using System; 5 | using System.Linq; 6 | using System.Text; 7 | 8 | namespace DeepClone.Template 9 | { 10 | public class CloneArrayTemplate : ICloneTemplate 11 | { 12 | internal readonly static int HashCode; 13 | static CloneArrayTemplate() => HashCode = typeof(CloneArrayTemplate).GetHashCode(); 14 | 15 | public override int GetHashCode() => HashCode; 16 | 17 | public bool MatchType(Type type) => type.IsArray; 18 | 19 | 20 | public Delegate TypeRouter(NBuildInfo info) 21 | { 22 | 23 | string methodBody; 24 | 25 | // 简单类型 26 | // SimpleType 27 | if (info.ArrayBaseType.IsSimpleType()) 28 | { 29 | 30 | methodBody = GenerateSimpleTypeClone(info); 31 | 32 | } 33 | else if (info.ArrayBaseType == typeof(object)) 34 | { 35 | 36 | // 多维+复杂 37 | // MultiD+Complex 38 | if (info.ArrayDimensions > 1) 39 | { 40 | methodBody = GenerateMultiDWithObjectClone(info); 41 | } 42 | // 1维+复杂 及 锯齿+复杂 43 | // 1D+Complex and Jagged+Complex 44 | else 45 | { 46 | methodBody = GenerateJaggedWithObjectClone2(info); 47 | } 48 | 49 | } 50 | else 51 | { 52 | 53 | // 多维+复杂 54 | // MultiD+Complex 55 | if (info.ArrayDimensions > 1) 56 | { 57 | methodBody = GenerateMultiDWithComplexClone(info); 58 | } 59 | // 1维+复杂 及 锯齿+复杂 60 | // 1D+Complex and Jagged+Complex 61 | else 62 | { 63 | methodBody = GenerateJaggedWithComplexClone2(info); 64 | } 65 | 66 | } 67 | 68 | 69 | return FastMethodOperator.UseDomain(info.CurrentType.GetDomain()) 70 | .Using("DeepClone") 71 | .Using(typeof(Array)) 72 | .Param(info.FatherType, "oldIns") 73 | .Body(methodBody) 74 | .Return(info.FatherType) 75 | .Compile(); 76 | } 77 | 78 | 79 | 80 | 81 | 82 | /// 83 | /// 1维+复杂 及 锯齿+复杂 84 | /// 1D+Complex 及 Jagged+Complex 85 | /// 86 | /// 87 | /// 88 | private string GenerateJaggedWithComplexClone2(NBuildInfo info) 89 | { 90 | var methodBody = $@" 91 | if(oldIns==default) return default; 92 | {info.CurrentTypeName} newIns = 93 | ({info.CurrentTypeName})Array.CreateInstance( 94 | typeof({info.ElementTypeName}) 95 | , oldIns.Length 96 | ); 97 | for (int i = 0; i < newIns.Length; i++) 98 | newIns[i] = CloneOperator.Clone(oldIns[i]); 99 | return newIns; 100 | "; 101 | return methodBody; 102 | } 103 | 104 | 105 | 106 | 107 | /// 108 | /// 1维+复杂 及 锯齿+复杂 109 | /// 1D+Complex 及 Jagged+Complex 110 | /// 111 | /// 112 | /// 113 | private string GenerateJaggedWithObjectClone2(NBuildInfo info) 114 | { 115 | var methodBody = $@" 116 | if(oldIns==default) return default; 117 | {info.CurrentTypeName} newIns = 118 | ({info.CurrentTypeName})Array.CreateInstance( 119 | typeof({info.ElementTypeName}) 120 | , oldIns.Length 121 | ); 122 | for (int i = 0; i < newIns.Length; i++) 123 | newIns[i] = ObjectCloneOperator.Clone(oldIns[i]); 124 | return newIns; 125 | "; 126 | return methodBody; 127 | } 128 | 129 | 130 | 131 | 132 | 133 | /// 134 | /// 多维+复杂 135 | /// MultiD+Complex 136 | /// 137 | /// 138 | /// 139 | private string GenerateMultiDWithComplexClone(NBuildInfo info) 140 | { 141 | var sb = new StringBuilder(); 142 | var varNameArr = new string[info.ArrayDimensions]; 143 | var multiArrLenArr = new string[info.ArrayDimensions]; 144 | 145 | for (int i = 0; i < info.ArrayDimensions; i++) 146 | { 147 | var varName = $"_{i}"; 148 | varNameArr[i] = varName; 149 | multiArrLenArr[i] = $"oldIns.GetLength({i})"; 150 | 151 | sb.AppendLine($"for (int {varName} = 0; {varName} < oldIns.GetLength({i}); {varName}++)"); 152 | } 153 | var varNameStr = string.Join(",", varNameArr); 154 | var multiArrTypeStr = string.Join(",", multiArrLenArr); 155 | 156 | var methodBody = $@" 157 | if(oldIns==default) return default; 158 | {info.CurrentTypeName} newIns = new {info.ElementTypeName}[{multiArrTypeStr}]; 159 | {sb.ToString()} 160 | newIns[{varNameStr}] = CloneOperator.Clone(oldIns[{varNameStr}]); 161 | return newIns; 162 | "; 163 | return methodBody; 164 | } 165 | 166 | 167 | 168 | 169 | /// 170 | /// 多维+复杂 171 | /// MultiD+Complex 172 | /// 173 | /// 174 | /// 175 | private string GenerateMultiDWithObjectClone(NBuildInfo info) 176 | { 177 | var sb = new StringBuilder(); 178 | var varNameArr = new string[info.ArrayDimensions]; 179 | var multiArrLenArr = new string[info.ArrayDimensions]; 180 | 181 | for (int i = 0; i < info.ArrayDimensions; i++) 182 | { 183 | var varName = $"_{i}"; 184 | varNameArr[i] = varName; 185 | multiArrLenArr[i] = $"oldIns.GetLength({i})"; 186 | 187 | sb.AppendLine($"for (int {varName} = 0; {varName} < oldIns.GetLength({i}); {varName}++)"); 188 | } 189 | var varNameStr = string.Join(",", varNameArr); 190 | var multiArrTypeStr = string.Join(",", multiArrLenArr); 191 | 192 | var methodBody = $@" 193 | if(oldIns==default) return default; 194 | {info.CurrentTypeName} newIns = new {info.ElementTypeName}[{multiArrTypeStr}]; 195 | {sb.ToString()} 196 | newIns[{varNameStr}] = ObjectCloneOperator.Clone(oldIns[{varNameStr}]); 197 | return newIns; 198 | "; 199 | return methodBody; 200 | } 201 | 202 | 203 | 204 | 205 | /// 206 | /// 简单类型 207 | /// SimpleType 208 | /// 209 | /// 210 | /// 211 | private string GenerateSimpleTypeClone(NBuildInfo info) 212 | { 213 | var sb = new StringBuilder(); 214 | // 多维 & 1维 215 | // MultiD & 1D 216 | if (info.ArrayDimensions >= 1 && info.ArrayLayer == 1) 217 | { 218 | // 生成多维数组结构 219 | // Generate a multi array structure 220 | var multiArrLenArr = new string[info.ArrayDimensions]; 221 | for (int i = 0; i < info.ArrayDimensions; i++) 222 | { 223 | multiArrLenArr[i] = $"oldIns.GetLength({i})"; 224 | } 225 | var multiArrTypeStr = string.Join(",", multiArrLenArr); 226 | 227 | sb.AppendLine($@" 228 | if(oldIns==default) return default; 229 | {info.CurrentTypeName} newIns = new {info.ElementTypeName}[{multiArrTypeStr}]; 230 | Array.Copy(oldIns, newIns, newIns.Length); 231 | return newIns; 232 | "); 233 | } 234 | // 锯齿 235 | // Jagged 236 | else 237 | { 238 | // 生成锯齿数组结构 239 | // Generating jagged array structure 240 | var arrItem = Enumerable.Repeat("[]", info.ArrayLayer - 1); 241 | sb.AppendLine($@" 242 | if(oldIns==default) return default; 243 | {info.CurrentTypeName} newIns = new {info.ArrayBaseTypeName}[oldIns.Length]{string.Join(string.Empty, arrItem)}; 244 | Array.Copy(oldIns, newIns, newIns.Length); 245 | return newIns; 246 | "); 247 | } 248 | return sb.ToString(); 249 | } 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /test/DeepCloneUT/Mocker.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace DeepCloneUT 5 | { 6 | public static class Mocker 7 | { 8 | private static Random random = new Random(); 9 | private static int RandomInt => (int)random.Next(int.MinValue, int.MaxValue); 10 | public readonly static char[] Element = new char[] { '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'd', 'e', 'f', 'h', 'k', 'm', 'n', 'r', 'x', 'y', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', 'R', 'S', 'T', 'W', 'X', 'Y' }; 11 | 12 | public static char[] MockArrayChar(int? len = null, bool notNull = false) 13 | { 14 | char[] result = null; 15 | if (!notNull) 16 | if (random.Next(0, 10) == 5) 17 | return result; 18 | 19 | len = len ?? random.Next(1, 100); 20 | result = new char[len.Value]; 21 | for (int i = 0; i < len; i++) 22 | result[i] = Element[random.Next(0, Element.Length)]; 23 | 24 | return result; 25 | } 26 | 27 | public static string RandomStr(int? len = null, bool notNull = false) 28 | { 29 | var res = MockArrayChar(len); 30 | return res == null ? null : string.Join(string.Empty, res); 31 | } 32 | 33 | #region SimpleType 34 | 35 | public static List MockListBoolean(int? len = null, bool notNull = false) 36 | { 37 | List result = null; 38 | if (!notNull) 39 | if (random.Next(0, 10) == 5) 40 | return result; 41 | 42 | result = new List(); 43 | len = len ?? random.Next(1, 100); 44 | for (int i = 0; i < len; i++) 45 | result.Add(random.Next(0, 2) == 1); 46 | 47 | return result; 48 | } 49 | public static bool[] MockArrayBoolean(int? len = null, bool notNull = false) => MockListBoolean(len, notNull)?.ToArray(); 50 | 51 | public static List MockListGenderEnum(int? len = null, bool notNull = false) 52 | { 53 | List result = null; 54 | if (!notNull) 55 | if (random.Next(0, 10) == 5) 56 | return result; 57 | 58 | result = new List(); 59 | len = len ?? random.Next(1, 100); 60 | for (int i = 0; i < len; i++) 61 | result.Add((GenderEnum)random.Next(0, 2)); 62 | 63 | return result; 64 | } 65 | public static GenderEnum[] MockArrayGenderEnum(int? len = null, bool notNull = false) => MockListGenderEnum(len, notNull)?.ToArray(); 66 | 67 | public static List MockListInt(int? len = null, bool notNull = false) 68 | { 69 | List result = null; 70 | if (!notNull) 71 | if (random.Next(0, 10) == 5) 72 | return result; 73 | 74 | result = new List(); 75 | len = len ?? random.Next(1, 100); 76 | for (int i = 0; i < len; i++) 77 | result.Add((int)(random.NextDouble() * int.MaxValue) * (random.Next(0, 2) == 0 ? 1 : -1)); 78 | 79 | return result; 80 | } 81 | 82 | public static List[] MockArrayListInt(int? len = null, bool notNull = false) 83 | { 84 | List[] result = null; 85 | if (!notNull) 86 | if (random.Next(0, 10) == 5) 87 | return result; 88 | 89 | len = len ?? random.Next(1, 100); 90 | result = new List[len.Value]; 91 | for (int i = 0; i < len; i++) 92 | result[i] = MockListInt(); 93 | 94 | return result; 95 | } 96 | 97 | public static int[] MockArrayInt(int? len = null, bool notNull = false) => MockListInt(len, notNull)?.ToArray(); 98 | 99 | public static List MockListLong(int? len = null, bool notNull = false) 100 | { 101 | List result = null; 102 | if (!notNull) 103 | if (random.Next(0, 10) == 5) 104 | return result; 105 | 106 | result = new List(); 107 | len = len ?? random.Next(1, 100); 108 | for (int i = 0; i < len; i++) 109 | result.Add((long)(random.NextDouble() * long.MaxValue) * (random.Next(0, 2) == 0 ? 1 : -1)); 110 | 111 | return result; 112 | } 113 | 114 | public static long[] MockArrayLong(int? len = null, bool notNull = false) => MockListLong(len, notNull)?.ToArray(); 115 | 116 | public static List MockListFloat(int? len = null, bool notNull = false) 117 | { 118 | List result = null; 119 | if (!notNull) 120 | if (random.Next(0, 10) == 5) 121 | return result; 122 | 123 | result = new List(); 124 | len = len ?? random.Next(1, 100); 125 | for (int i = 0; i < len; i++) 126 | result.Add((float)(random.NextDouble() * float.MaxValue) * (random.Next(0, 2) == 0 ? 1 : -1)); 127 | 128 | return result; 129 | } 130 | 131 | public static float[] MockArrayFloat(int? len = null, bool notNull = false) => MockListFloat(len, notNull)?.ToArray(); 132 | 133 | public static List MockListDouble(int? len = null, bool notNull = false) 134 | { 135 | List result = null; 136 | if (!notNull) 137 | if (random.Next(0, 10) == 5) 138 | return result; 139 | 140 | result = new List(); 141 | len = len ?? random.Next(1, 100); 142 | for (int i = 0; i < len; i++) 143 | result.Add((double)(random.NextDouble() * double.MaxValue) * (random.Next(0, 2) == 0 ? 1 : -1)); 144 | 145 | return result; 146 | } 147 | 148 | public static double[] MockArrayDouble(int? len = null, bool notNull = false) => MockListDouble(len, notNull)?.ToArray(); 149 | 150 | public static List MockListDecimal(int? len = null, bool notNull = false) 151 | { 152 | List result = null; 153 | if (!notNull) 154 | if (random.Next(0, 10) == 5) 155 | return result; 156 | 157 | result = new List(); 158 | len = len ?? random.Next(1, 100); 159 | for (int i = 0; i < len; i++) 160 | result.Add((decimal)((decimal)random.NextDouble() * decimal.MaxValue) * (random.Next(0, 2) == 0 ? 1 : -1)); 161 | 162 | return result; 163 | } 164 | 165 | public static decimal[] MockArrayDecimal(int? len = null, bool notNull = false) => MockListDecimal(len, notNull)?.ToArray(); 166 | 167 | public static List MockListShort(int? len = null, bool notNull = false) 168 | { 169 | List result = null; 170 | if (!notNull) 171 | if (random.Next(0, 10) == 5) 172 | return result; 173 | 174 | result = new List(); 175 | len = len ?? random.Next(1, 100); 176 | for (int i = 0; i < len; i++) 177 | result.Add((short)((random.NextDouble() * short.MaxValue) * (random.Next(0, 2) == 0 ? 1 : -1))); 178 | 179 | return result; 180 | } 181 | 182 | public static short[] MockArrayShort(int? len = null, bool notNull = false) => MockListShort(len, notNull)?.ToArray(); 183 | 184 | public static List MockListSbyte(int? len = null, bool notNull = false) 185 | { 186 | List result = null; 187 | if (!notNull) 188 | if (random.Next(0, 10) == 5) 189 | return result; 190 | 191 | result = new List(); 192 | len = len ?? random.Next(1, 100); 193 | for (int i = 0; i < len; i++) 194 | result.Add((sbyte)((random.NextDouble() * sbyte.MaxValue) * (random.Next(0, 2) == 0 ? 1 : -1))); 195 | 196 | return result; 197 | } 198 | 199 | public static sbyte[] MockArraySbyte(int? len = null, bool notNull = false) => MockListSbyte(len, notNull)?.ToArray(); 200 | 201 | public static List MockListByte(int? len = null, bool notNull = false) 202 | { 203 | List result = null; 204 | if (!notNull) 205 | if (random.Next(0, 10) == 5) 206 | return result; 207 | 208 | result = new List(); 209 | len = len ?? random.Next(1, 100); 210 | for (int i = 0; i < len; i++) 211 | result.Add((byte)(random.NextDouble() * byte.MaxValue)); 212 | 213 | return result; 214 | } 215 | 216 | public static byte[] MockArrayByte(int? len = null, bool notNull = false) => MockListByte(len, notNull)?.ToArray(); 217 | 218 | public static List MockListUShort(int? len = null, bool notNull = false) 219 | { 220 | List result = null; 221 | if (!notNull) 222 | if (random.Next(0, 10) == 5) 223 | return result; 224 | 225 | result = new List(); 226 | len = len ?? random.Next(1, 100); 227 | for (int i = 0; i < len; i++) 228 | result.Add((ushort)(random.NextDouble() * ushort.MaxValue)); 229 | 230 | return result; 231 | } 232 | 233 | public static ushort[] MockArrayUShort(int? len = null, bool notNull = false) => MockListUShort(len, notNull)?.ToArray(); 234 | 235 | public static List MockListUInt(int? len = null, bool notNull = false) 236 | { 237 | List result = null; 238 | if (!notNull) 239 | if (random.Next(0, 10) == 5) 240 | return result; 241 | 242 | result = new List(); 243 | len = len ?? random.Next(1, 100); 244 | for (int i = 0; i < len; i++) 245 | result.Add((uint)(random.NextDouble() * uint.MaxValue)); 246 | 247 | return result; 248 | } 249 | 250 | public static uint[] MockArrayUInt(int? len = null, bool notNull = false) => MockListUInt(len, notNull)?.ToArray(); 251 | 252 | public static List MockListULong(int? len = null, bool notNull = false) 253 | { 254 | List result = null; 255 | if (!notNull) 256 | if (random.Next(0, 10) == 5) 257 | return result; 258 | 259 | result = new List(); 260 | len = len ?? random.Next(1, 100); 261 | for (int i = 0; i < len; i++) 262 | result.Add((ulong)(random.NextDouble() * ulong.MaxValue)); 263 | 264 | return result; 265 | } 266 | 267 | public static ulong[] MockArrayULong(int? len = null, bool notNull = false) => MockListULong(len, notNull)?.ToArray(); 268 | 269 | #endregion 270 | 271 | 272 | private static Member _mockIns() 273 | { 274 | return new Member() 275 | { 276 | MemberType = (MemberType)random.Next(0, 2), 277 | FirstName = RandomStr(), 278 | MiddleName = RandomStr(), 279 | LastName = RandomStr(), 280 | Age = random.Next(1, 100), 281 | Birthday = DateTime.Now.AddDays(random.Next(-365, 365)).Date, 282 | AnnualIncome = random.Next(1, 1000000) / 13456.234M, 283 | Teacher = null 284 | }; 285 | } 286 | public static List MockListMember(int? len = null, bool notNull = false) 287 | { 288 | List result = null; 289 | if (!notNull) 290 | if (random.Next(0, 10) == 5) 291 | return result; 292 | 293 | result = new List(); 294 | len = len ?? random.Next(1, 100); 295 | for (int i = 0; i < len; i++) 296 | result.Add(MockMemberIns()); 297 | return result; 298 | } 299 | 300 | public static Member[] MockArrayMember(int? len = null, bool notNull = false) => MockListMember(len, notNull)?.ToArray(); 301 | 302 | public static Member MockMemberIns() 303 | { 304 | Member teacher = null; 305 | if (random.Next(0, 2) == 1) 306 | { 307 | teacher = _mockIns(); 308 | teacher.MemberType = MemberType.Teacher; 309 | } 310 | var result = _mockIns(); 311 | result.Teacher = teacher; 312 | return result; 313 | } 314 | 315 | public static Dictionary MockDict(int? len = null, bool notNull = false) 316 | { 317 | Dictionary result = null; 318 | if (!notNull) 319 | if (random.Next(0, 10) == 5) 320 | return result; 321 | 322 | result = new Dictionary(); 323 | len = len ?? random.Next(1, 100); 324 | for (int i = 0; i < len; i++) 325 | { 326 | var key = random.Next(int.MinValue, int.MaxValue); 327 | if (!result.ContainsKey(key)) 328 | result.Add(key, random.Next(int.MinValue, int.MaxValue)); 329 | } 330 | return result; 331 | } 332 | 333 | public static Dictionary[] MockArrayDict(int? len = null, bool notNull = false) 334 | { 335 | Dictionary[] result = null; 336 | if (!notNull) 337 | if (random.Next(0, 10) == 5) 338 | return result; 339 | 340 | len = len ?? random.Next(1, 100); 341 | result = new Dictionary[len.Value]; 342 | for (int i = 0; i < len; i++) 343 | result[i] = MockDict(); 344 | return result; 345 | } 346 | 347 | public static List MockList(int? len = null, bool notNull = false) 348 | { 349 | List result = null; 350 | if (!notNull) 351 | if (random.Next(0, 10) == 5) 352 | return result; 353 | 354 | result = new List(); 355 | len = len ?? random.Next(1, 100); 356 | for (int i = 0; i < len; i++) 357 | result.Add(random.Next(int.MinValue, int.MaxValue)); 358 | return result; 359 | } 360 | 361 | 362 | public static List MockListStr(int? len = null, bool notNull = false) 363 | { 364 | List result = null; 365 | if (!notNull) 366 | if (random.Next(0, 10) == 5) 367 | return result; 368 | 369 | result = new List(); 370 | len = len ?? random.Next(1, 100); 371 | for (int i = 0; i < len; i++) 372 | result.Add(RandomStr()); 373 | return result; 374 | } 375 | 376 | public static string[] MockArrayStr(int? len = null, bool notNull = false) => MockListStr(len, notNull)?.ToArray(); 377 | } 378 | } -------------------------------------------------------------------------------- /test/DeepCloneUT/NatashaUtTest.cs: -------------------------------------------------------------------------------- 1 | using DeepClone; 2 | using global::NatashaUT.Model; 3 | using System; 4 | using System.Collections.Generic; 5 | using Xunit; 6 | 7 | 8 | namespace DeepCloneUT 9 | { 10 | 11 | namespace NatashaUT 12 | { 13 | 14 | 15 | [Trait("克隆测试", "")] 16 | public class DynamicCloneTest : Prepare 17 | { 18 | 19 | [Fact(DisplayName = "字段--基元类型以及结构体")] 20 | public void Normal() 21 | { 22 | FieldCloneNormalModel model = new FieldCloneNormalModel 23 | { 24 | Age = 1000, 25 | Name = "ababab", 26 | Timer = DateTime.Now, 27 | money = 100000, 28 | Flag = CloneEnum.A, 29 | Title = false, 30 | Id = 100000 31 | }; 32 | 33 | var newModel = CloneOperator.Clone(model); 34 | Assert.Equal(model.Id, newModel.Id); 35 | Assert.Equal(model.Title, newModel.Title); 36 | Assert.Equal(model.money, newModel.money); 37 | Assert.Equal(model.Timer, newModel.Timer); 38 | Assert.Equal(model.Age, newModel.Age); 39 | Assert.Equal(model.Name, newModel.Name); 40 | } 41 | 42 | [Fact(DisplayName = "字段--时间以及非类数组")] 43 | public void NotClassArray() 44 | { 45 | FieldCloneArrayModel model = new FieldCloneArrayModel 46 | { 47 | Name = new string[10] 48 | }; 49 | for (int i = 0; i < 10; i++) 50 | { 51 | model.Name[i] = i.ToString(); 52 | } 53 | 54 | var newModel = CloneOperator.Clone(model); 55 | 56 | 57 | for (int i = 0; i < 10; i++) 58 | { 59 | Assert.Equal(model.Name[i], newModel.Name[i]); 60 | } 61 | } 62 | 63 | 64 | 65 | [Fact(DisplayName = "字段--类数组")] 66 | public void ClassArray() 67 | { 68 | FieldCloneClassArrayModel model = new FieldCloneClassArrayModel 69 | { 70 | Models = new FieldCloneNormalModel[10] 71 | }; 72 | for (int i = 0; i < 10; i++) 73 | { 74 | model.Models[i] = new FieldCloneNormalModel() { Age = i, Name = i.ToString() }; 75 | } 76 | 77 | var newModel = CloneOperator.Clone(model); 78 | 79 | 80 | for (int i = 0; i < 10; i++) 81 | { 82 | Assert.Equal(model.Models[i].Name, newModel.Models[i].Name); 83 | Assert.Equal(model.Models[i].Age, newModel.Models[i].Age); 84 | } 85 | } 86 | 87 | 88 | 89 | [Fact(DisplayName = "字段--子节点")] 90 | public void SubClassArray() 91 | { 92 | FieldCloneSubNodeModel model = new FieldCloneSubNodeModel 93 | { 94 | Node = new FieldCloneNormalModel() { Age = 1, Name = "111" } 95 | }; 96 | 97 | var newModel = CloneOperator.Clone(model); 98 | 99 | Assert.Equal(model.Node.Name, newModel.Node.Name); 100 | Assert.Equal(model.Node.Age, newModel.Node.Age); 101 | } 102 | 103 | 104 | 105 | [Fact(DisplayName = "字段--类集合")] 106 | public void ClassCollectionArray() 107 | { 108 | FieldCloneClassCollectionModel model = new FieldCloneClassCollectionModel 109 | { 110 | Nodes = new List() 111 | }; 112 | for (int i = 0; i < 10; i++) 113 | { 114 | model.Nodes.Add(new FieldCloneNormalModel() { Age = i, Name = i.ToString() }); 115 | } 116 | 117 | var newModel = CloneOperator.Clone(model); 118 | for (int i = 0; i < 10; i++) 119 | { 120 | Assert.Equal(model.Nodes[i].Name, newModel.Nodes[i].Name); 121 | Assert.Equal(model.Nodes[i].Age, newModel.Nodes[i].Age); 122 | Assert.NotEqual(i - 1, newModel.Nodes[i].Age); 123 | } 124 | model.Nodes.Clear(); 125 | Assert.NotEqual(model.Nodes.Count, newModel.Nodes.Count); 126 | } 127 | 128 | 129 | 130 | [Fact(DisplayName = "属性--基元类型以及结构体")] 131 | public void PropNormal() 132 | { 133 | PropCloneNormalModel model = new PropCloneNormalModel 134 | { 135 | Age = 1000, 136 | Name = "ababab", 137 | Timer = DateTime.Now, 138 | money = 100000, 139 | 140 | Title = false, 141 | Id = 100000 142 | }; 143 | 144 | var newModel = CloneOperator.Clone(model); 145 | Assert.Equal(model.Id, newModel.Id); 146 | Assert.Equal(model.Title, newModel.Title); 147 | Assert.Equal(model.money, newModel.money); 148 | Assert.Equal(model.Timer, newModel.Timer); 149 | Assert.Equal(model.Age, newModel.Age); 150 | Assert.Equal(model.Name, newModel.Name); 151 | } 152 | 153 | 154 | 155 | [Fact(DisplayName = "只读构造")] 156 | public void ReadonlyNormal() 157 | { 158 | PropCloneNormalModel model = new PropCloneNormalModel 159 | { 160 | Age = 1000, 161 | Name = "ababab", 162 | Timer = DateTime.Now, 163 | money = 100000, 164 | 165 | Title = false, 166 | Id = 100000 167 | }; 168 | 169 | var newModel = CloneOperator.Clone(model); 170 | Assert.False(newModel.NoUseCtor); 171 | Assert.Equal(0, newModel.ReadOnly); 172 | Assert.Equal(model.ReadOnlyString, newModel.ReadOnlyString); 173 | Assert.Equal(model.ReadOnlyString1, newModel.ReadOnlyString1); 174 | Assert.Equal(model.Id, newModel.Id); 175 | Assert.Equal(model.Title, newModel.Title); 176 | Assert.Equal(model.money, newModel.money); 177 | Assert.Equal(model.Timer, newModel.Timer); 178 | Assert.Equal(model.Age, newModel.Age); 179 | Assert.Equal(model.Name, newModel.Name); 180 | } 181 | 182 | 183 | 184 | 185 | [Fact(DisplayName = "属性--时间以及非类数组")] 186 | public void PropNotClassArray() 187 | { 188 | PropCloneArrayModel model = new PropCloneArrayModel 189 | { 190 | Name = new string[10] 191 | }; 192 | for (int i = 0; i < 10; i++) 193 | { 194 | model.Name[i] = i.ToString(); 195 | } 196 | 197 | var newModel = CloneOperator.Clone(model); 198 | 199 | 200 | for (int i = 0; i < 10; i++) 201 | { 202 | Assert.Equal(model.Name[i], newModel.Name[i]); 203 | } 204 | } 205 | 206 | 207 | 208 | [Fact(DisplayName = "属性--类数组")] 209 | public void PropClassArray() 210 | { 211 | PropCloneClassArrayModel model = new PropCloneClassArrayModel 212 | { 213 | Models = new PropCloneNormalModel[10] 214 | }; 215 | for (int i = 0; i < 10; i++) 216 | { 217 | model.Models[i] = new PropCloneNormalModel() { Age = i, Name = i.ToString() }; 218 | } 219 | 220 | var newModel = CloneOperator.Clone(model); 221 | 222 | 223 | for (int i = 0; i < 10; i++) 224 | { 225 | Assert.Equal(model.Models[i].Name, newModel.Models[i].Name); 226 | Assert.Equal(model.Models[i].Age, newModel.Models[i].Age); 227 | } 228 | 229 | } 230 | 231 | 232 | 233 | [Fact(DisplayName = "属性--子节点")] 234 | public void PropSubClassArray() 235 | { 236 | PropCloneSubNodeModel model = new PropCloneSubNodeModel 237 | { 238 | Node = new PropCloneNormalModel() { Age = 1, Name = "111" } 239 | }; 240 | 241 | 242 | var newModel = CloneOperator.Clone(model); 243 | 244 | 245 | Assert.Equal(model.Node.Name, newModel.Node.Name); 246 | Assert.Equal(model.Node.Age, newModel.Node.Age); 247 | } 248 | 249 | 250 | 251 | [Fact(DisplayName = "属性--类集合")] 252 | public void PropClassCollectionTest() 253 | { 254 | PropCloneClassCollectionModel model = new PropCloneClassCollectionModel 255 | { 256 | Nodes = new List() 257 | }; 258 | for (int i = 0; i < 10; i++) 259 | { 260 | model.Nodes.Add(new PropCloneNormalModel() { Age = i, Name = i.ToString() }); 261 | } 262 | 263 | var newModel = CloneOperator.Clone(model); 264 | 265 | for (int i = 0; i < 10; i++) 266 | { 267 | Assert.NotEqual(model.Nodes, newModel.Nodes); 268 | Assert.Equal(model.Nodes[i].Name, newModel.Nodes[i].Name); 269 | Assert.Equal(model.Nodes[i].Age, newModel.Nodes[i].Age); 270 | } 271 | 272 | } 273 | 274 | 275 | 276 | [Fact(DisplayName = "类集合嵌套集合")] 277 | public void PropClassCollectionArray1() 278 | { 279 | CloneCollectionModel model = new CloneCollectionModel(); 280 | var INodes = new List>(); 281 | for (int i = 0; i < 5; i++) 282 | { 283 | INodes.Add(new List()); 284 | for (int j = 0; j < 10; j++) 285 | { 286 | INodes[i].Add(new PropCloneNormalModel() { Age = j, Name = j.ToString() }); 287 | } 288 | } 289 | model.LLNodes = INodes; 290 | var newModel = CloneOperator.Clone(model); 291 | 292 | Assert.NotEqual(model.LLNodes, newModel.LLNodes); 293 | var oldNodes = new List>(model.LLNodes); 294 | var newNodes = new List>(newModel.LLNodes); 295 | for (int i = 0; i < 5; i++) 296 | { 297 | for (int j = 0; j < 10; j++) 298 | { 299 | Assert.NotEqual(oldNodes[i], newNodes[i]); 300 | Assert.Equal(oldNodes[i][j].Name, newNodes[i][j].Name); 301 | Assert.Equal(oldNodes[i][j].Age, newNodes[i][j].Age); 302 | } 303 | } 304 | } 305 | 306 | 307 | 308 | [Fact(DisplayName = "类集合嵌套数组")] 309 | public void PropClassCollectionArray2() 310 | { 311 | CloneCollectionModel model = new CloneCollectionModel 312 | { 313 | LANodes = new List() 314 | }; 315 | for (int i = 0; i < 5; i++) 316 | { 317 | model.LANodes.Add(new PropCloneNormalModel[10]); 318 | for (int j = 0; j < 10; j++) 319 | { 320 | model.LANodes[i][j] = new PropCloneNormalModel() { Age = j, Name = j.ToString() }; 321 | } 322 | } 323 | 324 | 325 | var newModel = CloneOperator.Clone(model); 326 | 327 | for (int i = 0; i < 5; i++) 328 | { 329 | Assert.NotEqual(model.LANodes, newModel.LANodes); 330 | for (int j = 0; j < 10; j++) 331 | { 332 | Assert.NotEqual(model.LANodes[i], newModel.LANodes[i]); 333 | Assert.Equal(model.LANodes[i][j].Name, newModel.LANodes[i][j].Name); 334 | Assert.Equal(model.LANodes[i][j].Age, newModel.LANodes[i][j].Age); 335 | } 336 | } 337 | } 338 | 339 | 340 | 341 | [Fact(DisplayName = "类数组嵌套集合")] 342 | public void PropClassCollectionArray3() 343 | { 344 | CloneCollectionModel model = new CloneCollectionModel 345 | { 346 | ALNodes = new List[5] 347 | }; 348 | for (int i = 0; i < 5; i++) 349 | { 350 | model.ALNodes[i] = new List(); 351 | for (int j = 0; j < 10; j++) 352 | { 353 | model.ALNodes[i].Add(new PropCloneNormalModel() { Age = j, Name = j.ToString() }); 354 | } 355 | } 356 | 357 | var newModel = CloneOperator.Clone(model); 358 | 359 | for (int i = 0; i < 5; i++) 360 | { 361 | Assert.NotEqual(model.ALNodes, newModel.ALNodes); 362 | for (int j = 0; j < 10; j++) 363 | { 364 | Assert.NotEqual(model.ALNodes[i], newModel.ALNodes[i]); 365 | Assert.Equal(model.ALNodes[i][j].Name, newModel.ALNodes[i][j].Name); 366 | Assert.Equal(model.ALNodes[i][j].Age, newModel.ALNodes[i][j].Age); 367 | } 368 | } 369 | } 370 | 371 | [Fact(DisplayName = "字段字典")] 372 | public void CloneDictionaryTest() 373 | { 374 | CloneDictModel model = new CloneDictModel 375 | { 376 | Dicts = new Dictionary() 377 | }; 378 | model.Dicts["1"] = "2"; 379 | model.Dicts["2"] = "3"; 380 | var newModel = CloneOperator.Clone(model); 381 | foreach (var item in newModel.Dicts) 382 | { 383 | Assert.Equal(model.Dicts[item.Key], item.Value); 384 | } 385 | model.Dicts["1"] = "4"; 386 | Assert.NotEqual(model.Dicts, newModel.Dicts); 387 | } 388 | 389 | 390 | [Fact(DisplayName = "字典集合")] 391 | public void CloneDictionaryCollectionTest() 392 | { 393 | CloneDictCollectionModel model = new CloneDictCollectionModel 394 | { 395 | Dicts = new Dictionary, List>() 396 | }; 397 | var key1 = new List() { "1" }; 398 | var key2 = new List() { "2" }; 399 | model.Dicts[key1] = new List(){ new FieldCloneNormalModel 400 | { 401 | Age = 1000, 402 | Name = "ababab", 403 | Timer = DateTime.Now, 404 | money = 100000, 405 | Flag = CloneEnum.A, 406 | Title = false, 407 | Id = 100000 408 | } }; 409 | model.Dicts[key2] = new List(){ new FieldCloneNormalModel 410 | { 411 | Age = 1000, 412 | Name = "ababab1", 413 | Timer = DateTime.Now, 414 | money = 100000, 415 | Flag = CloneEnum.B, 416 | Title = true, 417 | Id = 0 418 | } }; 419 | var newModel = CloneOperator.Clone(model); 420 | 421 | int i = 0; 422 | foreach (var item in newModel.Dicts) 423 | { 424 | if (i == 0) 425 | { 426 | key1.Add("1b"); 427 | Assert.NotEqual(item.Key, key1); 428 | Assert.Equal(model.Dicts[key1][0].Name, item.Value[0].Name); 429 | model.Dicts[key1].Clear(); 430 | Assert.NotEqual(model.Dicts[key1].Count, item.Value.Count); 431 | } 432 | else 433 | { 434 | Assert.Equal(item.Key, key2); 435 | Assert.Equal(model.Dicts[key2][0].Name, item.Value[0].Name); 436 | model.Dicts[key2].Clear(); 437 | Assert.NotEqual(model.Dicts[key2].Count, item.Value.Count); 438 | 439 | } 440 | i += 1; 441 | } 442 | Assert.NotSame(model.Dicts, newModel.Dicts); 443 | } 444 | 445 | 446 | [Fact(DisplayName = "字典数组")] 447 | public void CloneDictionaryArrayTest() 448 | { 449 | CloneDictArrayModel model = new CloneDictArrayModel 450 | { 451 | Dicts = new Dictionary[5] 452 | }; 453 | for (int i = 0; i < 5; i++) 454 | { 455 | model.Dicts[i] = new Dictionary(); 456 | for (int j = 0; j < 5; j++) 457 | { 458 | model.Dicts[i][j.ToString()] = new FieldCloneNormalModel[5]; 459 | for (int z = 0; z < 5; z++) 460 | { 461 | model.Dicts[i][j.ToString()][z] = new FieldCloneNormalModel 462 | { 463 | Age = 1000, 464 | Name = "ababab1", 465 | Timer = DateTime.Now, 466 | money = 100000, 467 | Flag = CloneEnum.B, 468 | Title = true, 469 | Id = 0 470 | }; 471 | } 472 | } 473 | } 474 | 475 | var newModel = CloneOperator.Clone(model); 476 | for (int i = 0; i < 5; i++) 477 | { 478 | 479 | Assert.Equal(model.Dicts[i].Count, newModel.Dicts[i].Count); 480 | for (int j = 0; j < 5; j++) 481 | { 482 | 483 | Assert.Equal(model.Dicts[i][j.ToString()].Length, newModel.Dicts[i][j.ToString()].Length); 484 | for (int z = 0; z < 5; z++) 485 | { 486 | 487 | Assert.Equal(model.Dicts[i][j.ToString()][z].Name, newModel.Dicts[i][j.ToString()][z].Name); 488 | Assert.Equal(model.Dicts[i][j.ToString()][z].Age, newModel.Dicts[i][j.ToString()][z].Age); 489 | Assert.Equal(model.Dicts[i][j.ToString()][z].Flag, newModel.Dicts[i][j.ToString()][z].Flag); 490 | Assert.Equal(model.Dicts[i][j.ToString()][z].Id, newModel.Dicts[i][j.ToString()][z].Id); 491 | Assert.Equal(model.Dicts[i][j.ToString()][z].Timer, newModel.Dicts[i][j.ToString()][z].Timer); 492 | Assert.Equal(model.Dicts[i][j.ToString()][z].Title, newModel.Dicts[i][j.ToString()][z].Title); 493 | } 494 | model.Dicts[i][j.ToString()] = new FieldCloneNormalModel[0]; 495 | Assert.NotEqual(model.Dicts[i][j.ToString()].Length, newModel.Dicts[i][j.ToString()].Length); 496 | } 497 | 498 | model.Dicts[i].Clear(); 499 | Assert.NotSame(model.Dicts[i], newModel.Dicts[i]); 500 | Assert.NotEqual(model.Dicts[i].Count, newModel.Dicts[i].Count); 501 | } 502 | 503 | } 504 | 505 | 506 | [Fact(DisplayName = "C#链表测试")] 507 | public void CloneLinkTest() 508 | { 509 | FieldLinkModel model = new FieldLinkModel(); 510 | model.Nodes.AddFirst(new FieldLinkModel() 511 | { 512 | Name = "1", 513 | Age = 1 514 | }); 515 | model.Nodes.AddLast(new FieldLinkModel() 516 | { 517 | Name = "2", 518 | Age = 2 519 | }); 520 | 521 | 522 | var newModel = CloneOperator.Clone(model); 523 | 524 | Assert.NotEqual(model.Nodes.First, newModel.Nodes.First); 525 | Assert.Equal(model.Nodes.First.Value.Name, newModel.Nodes.First.Value.Name); 526 | 527 | Assert.NotEqual(model.Nodes.First.Next, newModel.Nodes.First.Next); 528 | Assert.Equal(model.Nodes.First.Next.Value.Name, newModel.Nodes.First.Next.Value.Name); 529 | 530 | } 531 | 532 | [Fact(DisplayName = "C#链表数组测试")] 533 | public void CloneLinkArrayTest() 534 | { 535 | FieldLinkArrayModel model = new FieldLinkArrayModel 536 | { 537 | Nodes = new LinkedList[5] 538 | }; 539 | 540 | 541 | for (int i = 0; i < 5; i++) 542 | { 543 | model.Nodes[i] = new LinkedList(); 544 | model.Nodes[i].AddLast(new FieldLinkArrayModel() 545 | { 546 | Name = i.ToString(), 547 | Age = i 548 | }); 549 | model.Nodes[i].AddLast(new FieldLinkArrayModel() 550 | { 551 | Name = (i + 1).ToString(), 552 | Age = i + 1 553 | }); 554 | } 555 | 556 | 557 | var newModel = CloneOperator.Clone(model); 558 | 559 | Assert.NotNull(newModel.Nodes); 560 | Assert.NotEqual(model.Nodes, newModel.Nodes); 561 | Assert.Equal(model.Nodes.Length, newModel.Nodes.Length); 562 | 563 | for (int i = 0; i < 5; i++) 564 | { 565 | Assert.NotEqual(model.Nodes[i], newModel.Nodes[i]); 566 | Assert.NotEqual(model.Nodes[i].First, newModel.Nodes[i].First); 567 | Assert.Equal(model.Nodes[i].First.Value.Name, newModel.Nodes[i].First.Value.Name); 568 | Assert.NotEqual(model.Nodes[i].First.Next, newModel.Nodes[i].First.Next); 569 | Assert.Equal(model.Nodes[i].First.Next.Value.Name, newModel.Nodes[i].First.Next.Value.Name); 570 | } 571 | } 572 | 573 | 574 | [Fact(DisplayName = "自实现链表测试")] 575 | public void CloneSelfTest() 576 | { 577 | FieldSelfLinkModel model = new FieldSelfLinkModel() 578 | { 579 | Name = "1", 580 | Age = 1 581 | }; 582 | model.Next = new FieldSelfLinkModel() 583 | { 584 | Name = "2", 585 | Age = 2 586 | }; 587 | 588 | 589 | var newModel = CloneOperator.Clone(model); 590 | Assert.NotEqual(model.Next, newModel.Next); 591 | Assert.Equal(model.Next.Name, newModel.Next.Name); 592 | } 593 | 594 | 595 | [Fact(DisplayName = "自实现链表数组测试")] 596 | public void CloneArraySelfTest() 597 | { 598 | FieldSelfLinkArrayModel model = new FieldSelfLinkArrayModel() 599 | { 600 | Name = "1", 601 | Age = 1 602 | }; 603 | 604 | 605 | model.Next = new FieldSelfLinkArrayModel[5]; 606 | for (int i = 0; i < 5; i++) 607 | { 608 | model.Next[i] = new FieldSelfLinkArrayModel() 609 | { 610 | Name = i.ToString(), 611 | Age = i 612 | }; 613 | } 614 | 615 | 616 | var newModel = CloneOperator.Clone(model); 617 | Assert.NotEqual(model.Next, newModel.Next); 618 | for (int i = 0; i < 5; i++) 619 | { 620 | Assert.NotEqual(model.Next[i], newModel.Next[i]); 621 | Assert.Equal(model.Next[i].Name, newModel.Next[i].Name); 622 | } 623 | 624 | } 625 | } 626 | } 627 | } 628 | -------------------------------------------------------------------------------- /test/DeepCloneUT/ArrayTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using DeepClone; 5 | using DeepClone.Template; 6 | using DeepCloneUT.EqualityComparer; 7 | using Xunit; 8 | 9 | namespace DeepCloneUT 10 | { 11 | public class ArrayTest: Prepare 12 | { 13 | [Fact] 14 | public void IsArray() 15 | { 16 | var ins1 = new string[] { "Vito", "AzulX", "guodf", "wxn401", "myFirstway" }; 17 | var ins2 = new int[] { 1, 3, 5, 2, 4, 6 }; 18 | var ins3 = new float[][] { 19 | new float[] { 1.2F,1.2F,1.3F,1.4F }, 20 | new float[] { 2.2F,2.2F,2.3F,2.4F } 21 | }; 22 | var ins4 = new decimal[3, 2, 4] { 23 | { 24 | {1M,2M,3M,4M}, 25 | {5M,6M,7M,8M} 26 | }, 27 | { 28 | {1M,2M,3M,4M}, 29 | {5M,6M,7M,8M} 30 | }, 31 | { 32 | {1M,2M,3M,4M}, 33 | {5M,6M,7M,8M} 34 | } 35 | }; 36 | 37 | var arrayTemp = new CloneArrayTemplate(); 38 | 39 | Assert.True(arrayTemp.MatchType(ins1.GetType())); 40 | Assert.True(arrayTemp.MatchType(ins2.GetType())); 41 | Assert.True(arrayTemp.MatchType(ins3.GetType())); 42 | Assert.True(arrayTemp.MatchType(ins4.GetType())); 43 | } 44 | 45 | [Fact] 46 | public void NotArray() 47 | { 48 | var ins0 = new { Name = "Vito", Age = 17 }; 49 | var ins1 = 1; 50 | var ins2 = 1L; 51 | var ins3 = "string"; 52 | var ins4 = 1.3D; 53 | var ins5 = 1.4M; 54 | var ins6 = GenderEnum.Secrecy; 55 | var ins7 = new List() { "Vito", "AzulX", "guodf", "wxn401", "myFirstway" }; 56 | var ins8 = new List() { 1, 3, 5, 2, 4, 6 }; 57 | var ins9 = new List() { 58 | new int[,] { { 1, 2 }, { 2, 3 } }, 59 | new int[,] { { 1, 2 }, { 2, 3 }, { 2, 3 } }, 60 | new int[,] { { 1, 2, 3 }, { 3, 4, 5 } } 61 | }; 62 | var ins10 = new ArrayList() { "Vito", "AzulX", "guodf", "wxn401", "myFirstway", 1, 3, 5, 2, 4, 6, new { Name = "Vito", Age = 17 } }; 63 | var ins11 = new Dictionary(){ 64 | {"Key1","Value1"}, 65 | {"Key2","Value2"} 66 | }; 67 | 68 | var arrayTemp = new CloneArrayTemplate(); 69 | Assert.False(arrayTemp.MatchType(ins0.GetType())); 70 | Assert.False(arrayTemp.MatchType(ins1.GetType())); 71 | Assert.False(arrayTemp.MatchType(ins2.GetType())); 72 | Assert.False(arrayTemp.MatchType(ins3.GetType())); 73 | Assert.False(arrayTemp.MatchType(ins4.GetType())); 74 | Assert.False(arrayTemp.MatchType(ins5.GetType())); 75 | Assert.False(arrayTemp.MatchType(ins6.GetType())); 76 | Assert.False(arrayTemp.MatchType(ins7.GetType())); 77 | Assert.False(arrayTemp.MatchType(ins8.GetType())); 78 | Assert.False(arrayTemp.MatchType(ins9.GetType())); 79 | Assert.False(arrayTemp.MatchType(ins10.GetType())); 80 | Assert.False(arrayTemp.MatchType(ins11.GetType())); 81 | } 82 | 83 | /// 84 | /// 1维度+简单: int[] 85 | /// 86 | [Fact] 87 | public void Clone1DimWithSimple() 88 | { 89 | string[] arrIns0 = null; 90 | var arrIns1 = new sbyte[] { 1, 2, 3, sbyte.MinValue, sbyte.MaxValue }; 91 | var arrIns2 = new short[] { 4, 5, 6, short.MinValue, short.MaxValue }; 92 | var arrIns3 = new int[] { 7, -8, 9, -10, int.MinValue, int.MaxValue }; 93 | var arrIns4 = new long[] { 12345, 23456, long.MinValue, long.MaxValue }; 94 | var arrIns5 = new byte[] { 123, 234, 56, byte.MinValue, byte.MaxValue }; 95 | var arrIns6 = new ushort[] { 123, 567, ushort.MinValue, ushort.MaxValue }; 96 | var arrIns7 = new uint[] { 678, 349, uint.MinValue, uint.MaxValue }; 97 | var arrIns8 = new ulong[] { 789, 2345, ulong.MinValue, ulong.MaxValue }; 98 | var arrIns9 = new float[] { 678.234F, 789, 234.000F, float.MinValue, float.MaxValue }; 99 | var arrIns10 = new double[] { 567, 678.56789, double.MinValue, double.MaxValue }; 100 | var arrIns11 = new decimal[] { 45678, 678.00M, 56789.234M, decimal.MinValue, decimal.MaxValue }; 101 | var arrIns12 = new GenderEnum[] { GenderEnum.Female, GenderEnum.Secrecy }; 102 | var arrIns13 = new bool[] { true, false, true }; 103 | var arrIns14 = new char[] { '1', '2', 'a', 'Z' }; 104 | var arrIns15 = new string[] { "Vito", "AzulX", "guodf", "wxn401", "myFirstway" }; 105 | 106 | var arrIns0_Clone = CloneOperator.Clone(arrIns0); 107 | var arrIns1_Clone = CloneOperator.Clone(arrIns1); 108 | var arrIns2_Clone = CloneOperator.Clone(arrIns2); 109 | var arrIns3_Clone = CloneOperator.Clone(arrIns3); 110 | var arrIns4_Clone = CloneOperator.Clone(arrIns4); 111 | var arrIns5_Clone = CloneOperator.Clone(arrIns5); 112 | var arrIns6_Clone = CloneOperator.Clone(arrIns6); 113 | var arrIns7_Clone = CloneOperator.Clone(arrIns7); 114 | var arrIns8_Clone = CloneOperator.Clone(arrIns8); 115 | var arrIns9_Clone = CloneOperator.Clone(arrIns9); 116 | var arrIns10_Clone = CloneOperator.Clone(arrIns10); 117 | var arrIns11_Clone = CloneOperator.Clone(arrIns11); 118 | var arrIns12_Clone = CloneOperator.Clone(arrIns12); 119 | var arrIns13_Clone = CloneOperator.Clone(arrIns13); 120 | var arrIns14_Clone = CloneOperator.Clone(arrIns14); 121 | var arrIns15_Clone = CloneOperator.Clone(arrIns15); 122 | 123 | Assert.Equal(arrIns0_Clone, arrIns0); 124 | Assert.Equal(arrIns1_Clone, arrIns1); 125 | Assert.Equal(arrIns2_Clone, arrIns2); 126 | Assert.Equal(arrIns3_Clone, arrIns3); 127 | Assert.Equal(arrIns4_Clone, arrIns4); 128 | Assert.Equal(arrIns5_Clone, arrIns5); 129 | Assert.Equal(arrIns6_Clone, arrIns6); 130 | Assert.Equal(arrIns7_Clone, arrIns7); 131 | Assert.Equal(arrIns8_Clone, arrIns8); 132 | Assert.Equal(arrIns9_Clone, arrIns9); 133 | Assert.Equal(arrIns10_Clone, arrIns10); 134 | Assert.Equal(arrIns11_Clone, arrIns11); 135 | Assert.Equal(arrIns12_Clone, arrIns12); 136 | Assert.Equal(arrIns13_Clone, arrIns13); 137 | Assert.Equal(arrIns14_Clone, arrIns14); 138 | Assert.Equal(arrIns15_Clone, arrIns15); 139 | } 140 | 141 | /// 142 | /// 1维度+复杂: object[] 143 | /// 144 | [Fact] 145 | public void Clone1DimWithComplex() 146 | { 147 | var random = new Random(); 148 | Member[] arrIns0 = null; 149 | // var arrIns1 = new object[] { new Member(), new Member(), new Member() }; 150 | var arrIns2 = Mocker.MockArrayMember(notNull: true); 151 | var arrIns3 = new Dictionary[] { 152 | Mocker.MockDict(), 153 | Mocker.MockDict() 154 | }; 155 | var arrIns4 = new List[] { 156 | Mocker.MockList(), 157 | Mocker.MockList() 158 | }; 159 | // var arrIns5 = new dynamic[] { }; 160 | 161 | var arrIns0_Clone = CloneOperator.Clone(arrIns0); 162 | // var arrIns1_Clone = CloneOperator.Clone(arrIns1); 163 | var arrIns2_Clone = CloneOperator.Clone(arrIns2); 164 | var arrIns3_Clone = CloneOperator.Clone(arrIns3); 165 | var arrIns4_Clone = CloneOperator.Clone(arrIns4); 166 | // var arrIns5_Clone = CloneOperator.Clone(arrIns5); 167 | 168 | Assert.Null(arrIns0_Clone); 169 | Assert.Equal(arrIns0_Clone, arrIns0); 170 | 171 | // Assert.Equal(arrIns1_Clone, arrIns1); 172 | 173 | Assert.NotNull(arrIns2_Clone); 174 | Assert.NotSame(arrIns2_Clone, arrIns2); 175 | Assert.True(arrIns2_Clone.Length == arrIns2.Length); 176 | Assert.Equal(arrIns2_Clone, arrIns2, new MemberArrayEqualityComparer()); 177 | 178 | Assert.NotNull(arrIns3_Clone); 179 | Assert.NotSame(arrIns3_Clone, arrIns3); 180 | Assert.True(arrIns3_Clone.Length == arrIns3.Length); 181 | Assert.Equal(arrIns3_Clone, arrIns3, new DictArrayEqualityComparer()); 182 | 183 | Assert.NotNull(arrIns4_Clone); 184 | Assert.NotSame(arrIns4_Clone, arrIns4); 185 | Assert.True(arrIns4_Clone.Length == arrIns4.Length); 186 | Assert.Equal(arrIns4_Clone, arrIns4, new ListArrayEqualityComparer()); 187 | 188 | // Assert.Equal(arrIns5_Clone, arrIns5); 189 | } 190 | 191 | /// 192 | /// 多维+简单: int[,,] 193 | /// 194 | [Fact] 195 | public void CloneMultiDimWithSimple() 196 | { 197 | string[,,] arrIns0 = null; 198 | var arrIns1 = new sbyte[,,] { 199 | { 200 | { 1, sbyte.MinValue, 0, 3, sbyte.MaxValue }, 201 | { 0, 2, sbyte.MaxValue, 23, sbyte.MinValue } 202 | }, 203 | { 204 | { 1, sbyte.MaxValue, 12, 3, sbyte.MinValue }, 205 | { sbyte.MinValue, 1, 41, 114, sbyte.MaxValue } 206 | } 207 | }; 208 | var arrIns2 = new short[,,] { 209 | { 210 | { 1, short.MinValue, 0, 3, short.MaxValue }, 211 | { 0, 2, short.MaxValue, 23, short.MinValue } 212 | }, 213 | { 214 | { 1, short.MaxValue, 12, 3, short.MinValue }, 215 | { short.MinValue, 1, 41, 114, short.MaxValue } 216 | } 217 | }; 218 | var arrIns3 = new int[,,] { 219 | { 220 | { 1, int.MinValue, 0, 3, int.MaxValue }, 221 | { 0, 2, int.MaxValue, 23, int.MinValue } 222 | }, 223 | { 224 | { 1, int.MaxValue, 12, 3, int.MinValue }, 225 | { int.MinValue, 1, 41, 114, int.MaxValue } 226 | } 227 | }; 228 | var arrIns4 = new long[,,] { 229 | { 230 | { 1, long.MinValue, 0, 3, long.MaxValue }, 231 | { 0, 2, long.MaxValue, 23, long.MinValue } 232 | }, 233 | { 234 | { 1, long.MaxValue, 12, 3, long.MinValue }, 235 | { long.MinValue, 1, 41, 114, long.MaxValue } 236 | } 237 | }; 238 | var arrIns5 = new byte[,,] { 239 | { 240 | { 1, byte.MinValue, 0, 3, byte.MaxValue }, 241 | { 0, 2, byte.MaxValue, 23, byte.MinValue } 242 | }, 243 | { 244 | { 1, byte.MaxValue, 12, 3, byte.MinValue }, 245 | { byte.MinValue, 1, 41, 114, byte.MaxValue } 246 | } 247 | }; 248 | var arrIns6 = new ushort[,,] { 249 | { 250 | { 1, ushort.MinValue, 0, 3, ushort.MaxValue }, 251 | { 0, 2, ushort.MaxValue, 23, ushort.MinValue } 252 | }, 253 | { 254 | { 1, ushort.MaxValue, 12, 3, ushort.MinValue }, 255 | { ushort.MinValue, 1, 41, 114, ushort.MaxValue } 256 | } 257 | }; 258 | var arrIns7 = new uint[,,] { 259 | { 260 | { 1, uint.MinValue, 0, 3, uint.MaxValue }, 261 | { 0, 2, uint.MaxValue, 23, uint.MinValue } 262 | }, 263 | { 264 | { 1, uint.MaxValue, 12, 3, uint.MinValue }, 265 | { uint.MinValue, 1, 41, 114, uint.MaxValue } 266 | } 267 | }; 268 | var arrIns8 = new ulong[,,] { 269 | { 270 | { 1, ulong.MinValue, 0, 3, ulong.MaxValue }, 271 | { 0, 2, ulong.MaxValue, 23, ulong.MinValue } 272 | }, 273 | { 274 | { 1, ulong.MaxValue, 12, 3, ulong.MinValue }, 275 | { ulong.MinValue, 1, 41, 114, ulong.MaxValue } 276 | } 277 | }; 278 | var arrIns9 = new float[,,] { 279 | { 280 | { 1, float.MinValue, 0, 3, float.MaxValue }, 281 | { 0, 2, float.MaxValue, 23, float.MinValue } 282 | }, 283 | { 284 | { 1, float.MaxValue, 12, 3, float.MinValue }, 285 | { float.MinValue, 1, 41, 114, float.MaxValue } 286 | } 287 | }; 288 | var arrIns10 = new double[,,] { 289 | { 290 | { 1, double.MinValue, 0, 3, double.MaxValue }, 291 | { 0, 2, double.MaxValue, 23, double.MinValue } 292 | }, 293 | { 294 | { 1, double.MaxValue, 12, 3, double.MinValue }, 295 | { double.MinValue, 1, 41, 114, double.MaxValue } 296 | } 297 | }; 298 | var arrIns11 = new decimal[,,] { 299 | { 300 | { 1, decimal.MinValue, 0, 3, decimal.MaxValue }, 301 | { 0, 2, decimal.MaxValue, 23, decimal.MinValue } 302 | }, 303 | { 304 | { 1, decimal.MaxValue, 12, 3, decimal.MinValue }, 305 | { decimal.MinValue, 1, 41, 114, decimal.MaxValue } 306 | } 307 | }; 308 | var arrIns12 = new GenderEnum[,,] { 309 | { 310 | { GenderEnum.Female,GenderEnum.Male,GenderEnum.Secrecy }, 311 | { GenderEnum.Male,GenderEnum.Secrecy,GenderEnum.Male }, 312 | }, 313 | { 314 | { GenderEnum.Female,GenderEnum.Secrecy,GenderEnum.Secrecy }, 315 | { GenderEnum.Male,GenderEnum.Male,GenderEnum.Female }, 316 | } 317 | }; 318 | var arrIns13 = new bool[,,] { 319 | { 320 | { true,false,true }, 321 | { false,true,true } 322 | }, 323 | { 324 | { false,false,true }, 325 | { false,true,false } 326 | } 327 | }; 328 | var arrIns14 = new char[,,] { 329 | { 330 | { '1','2','3' }, 331 | { 'a','c','.' } 332 | }, 333 | { 334 | { ')','+','@' }, 335 | { '/','%','$' } 336 | } 337 | }; 338 | var arrIns15 = new string[,,] { 339 | { 340 | {"wxn401", "AzulX", "guodf", "wxn401", "myFirstway"}, 341 | {"", "", "", "", ""}, 342 | {"Vito", "wxn401", "guodf", "AzulX", "myFirstway"} 343 | }, 344 | { 345 | {"Vito", "AzulX", "myFirstway", "wxn401", "myFirstway"}, 346 | {"myFirstway", "", "guodf", "wxn401", "myFirstway"}, 347 | {"Vito", "AzulX", "guodf", "AzulX", "AzulX"} 348 | } 349 | }; 350 | 351 | var arrIns0_Clone = CloneOperator.Clone(arrIns0); 352 | var arrIns1_Clone = CloneOperator.Clone(arrIns1); 353 | var arrIns2_Clone = CloneOperator.Clone(arrIns2); 354 | var arrIns3_Clone = CloneOperator.Clone(arrIns3); 355 | var arrIns4_Clone = CloneOperator.Clone(arrIns4); 356 | var arrIns5_Clone = CloneOperator.Clone(arrIns5); 357 | var arrIns6_Clone = CloneOperator.Clone(arrIns6); 358 | var arrIns7_Clone = CloneOperator.Clone(arrIns7); 359 | var arrIns8_Clone = CloneOperator.Clone(arrIns8); 360 | var arrIns9_Clone = CloneOperator.Clone(arrIns9); 361 | var arrIns10_Clone = CloneOperator.Clone(arrIns10); 362 | var arrIns11_Clone = CloneOperator.Clone(arrIns11); 363 | var arrIns12_Clone = CloneOperator.Clone(arrIns12); 364 | var arrIns13_Clone = CloneOperator.Clone(arrIns13); 365 | var arrIns14_Clone = CloneOperator.Clone(arrIns14); 366 | var arrIns15_Clone = CloneOperator.Clone(arrIns15); 367 | 368 | Assert.Equal(arrIns0_Clone, arrIns0); 369 | Assert.Equal(arrIns1_Clone, arrIns1); 370 | Assert.Equal(arrIns2_Clone, arrIns2); 371 | Assert.Equal(arrIns3_Clone, arrIns3); 372 | Assert.Equal(arrIns4_Clone, arrIns4); 373 | Assert.Equal(arrIns5_Clone, arrIns5); 374 | Assert.Equal(arrIns6_Clone, arrIns6); 375 | Assert.Equal(arrIns7_Clone, arrIns7); 376 | Assert.Equal(arrIns8_Clone, arrIns8); 377 | Assert.Equal(arrIns9_Clone, arrIns9); 378 | Assert.Equal(arrIns10_Clone, arrIns10); 379 | Assert.Equal(arrIns11_Clone, arrIns11); 380 | Assert.Equal(arrIns12_Clone, arrIns12); 381 | Assert.Equal(arrIns13_Clone, arrIns13); 382 | Assert.Equal(arrIns14_Clone, arrIns14); 383 | Assert.Equal(arrIns15_Clone, arrIns15); 384 | } 385 | 386 | /// 387 | /// 多维+复杂: object[,,] 388 | /// 389 | [Fact] 390 | public void CloneMultiDimWithComplex() 391 | { 392 | Member[,,] arrIns0 = null; 393 | // var arrIns1 = new object[,,]{ 394 | // { 395 | // {Mocker.MockMemberIns(),Mocker.MockMemberIns()}, 396 | // {Mocker.MockMemberIns(),Mocker.MockMemberIns()} 397 | // }, 398 | // { 399 | // {Mocker.MockMemberIns(),Mocker.MockMemberIns()}, 400 | // {Mocker.MockMemberIns(),Mocker.MockMemberIns()} 401 | // } 402 | // }; 403 | var arrIns2 = new Member[,,]{ 404 | { 405 | {Mocker.MockMemberIns(),Mocker.MockMemberIns()}, 406 | {Mocker.MockMemberIns(),Mocker.MockMemberIns()} 407 | }, 408 | { 409 | {Mocker.MockMemberIns(),Mocker.MockMemberIns()}, 410 | {Mocker.MockMemberIns(),Mocker.MockMemberIns()} 411 | } 412 | }; 413 | var arrIns3 = new Dictionary[,,]{ 414 | { 415 | {Mocker.MockDict(),Mocker.MockDict()}, 416 | {Mocker.MockDict(),Mocker.MockDict()} 417 | }, 418 | { 419 | {Mocker.MockDict(),Mocker.MockDict()}, 420 | {Mocker.MockDict(),Mocker.MockDict()} 421 | } 422 | }; 423 | var arrIns4 = new List[,,]{ 424 | { 425 | {Mocker.MockList(),Mocker.MockList()}, 426 | {Mocker.MockList(),Mocker.MockList()} 427 | }, 428 | { 429 | {Mocker.MockList(),Mocker.MockList()}, 430 | {Mocker.MockList(),Mocker.MockList()} 431 | } 432 | }; 433 | // var arrIns5 = new dynamic[,,]{ 434 | // { 435 | // {Mocker.MockMemberIns(),Mocker.MockMemberIns()}, 436 | // {Mocker.MockMemberIns(),Mocker.MockMemberIns()} 437 | // }, 438 | // { 439 | // {Mocker.MockMemberIns(),Mocker.MockMemberIns()}, 440 | // {Mocker.MockMemberIns(),Mocker.MockMemberIns()} 441 | // } 442 | // }; 443 | 444 | var arrIns0_Clone = CloneOperator.Clone(arrIns0); 445 | // var arrIns1_Clone = CloneOperator.Clone(arrIns1); 446 | var arrIns2_Clone = CloneOperator.Clone(arrIns2); 447 | var arrIns3_Clone = CloneOperator.Clone(arrIns3); 448 | var arrIns4_Clone = CloneOperator.Clone(arrIns4); 449 | // var arrIns5_Clone = CloneOperator.Clone(arrIns5); 450 | 451 | Assert.Null(arrIns0_Clone); 452 | Assert.Equal(arrIns0_Clone, arrIns0); 453 | 454 | // Assert.Equal(arrIns1_Clone, arrIns1); 455 | 456 | Assert.NotNull(arrIns2_Clone); 457 | Assert.NotSame(arrIns2_Clone, arrIns2); 458 | Assert.True(arrIns2_Clone.Length == arrIns2.Length); 459 | for (int _0 = 0; _0 < arrIns2_Clone.GetLength(0); _0++) 460 | for (int _1 = 0; _1 < arrIns2_Clone.GetLength(1); _1++) 461 | for (int _2 = 0; _2 < arrIns2_Clone.GetLength(2); _2++) 462 | Assert.Equal(arrIns2_Clone[_0, _1, _2], arrIns2[_0, _1, _2], new MemberArrayEqualityComparer()); 463 | 464 | Assert.NotNull(arrIns3_Clone); 465 | Assert.NotSame(arrIns3_Clone, arrIns3); 466 | Assert.True(arrIns3_Clone.Length == arrIns3.Length); 467 | for (int _0 = 0; _0 < arrIns3_Clone.GetLength(0); _0++) 468 | for (int _1 = 0; _1 < arrIns3_Clone.GetLength(1); _1++) 469 | for (int _2 = 0; _2 < arrIns3_Clone.GetLength(2); _2++) 470 | Assert.Equal(arrIns3_Clone[_0, _1, _2], arrIns3[_0, _1, _2], new DictArrayEqualityComparer()); 471 | 472 | Assert.NotNull(arrIns4_Clone); 473 | Assert.NotSame(arrIns4_Clone, arrIns4); 474 | Assert.True(arrIns4_Clone.Length == arrIns4.Length); 475 | for (int _0 = 0; _0 < arrIns4_Clone.GetLength(0); _0++) 476 | for (int _1 = 0; _1 < arrIns4_Clone.GetLength(1); _1++) 477 | for (int _2 = 0; _2 < arrIns4_Clone.GetLength(2); _2++) 478 | Assert.Equal(arrIns4_Clone[_0, _1, _2], arrIns4[_0, _1, _2], new ListArrayEqualityComparer()); 479 | 480 | // Assert.Equal(arrIns5_Clone, arrIns5); 481 | } 482 | 483 | /// 484 | /// 锯齿+简单: int[][][] 485 | /// 486 | [Fact] 487 | public void CloneJaggedWithSimple() 488 | { 489 | string[][][] arrIns0 = null; 490 | var arrIns1 = new sbyte[][][] { 491 | new sbyte[][]{ 492 | Mocker.MockArraySbyte(), 493 | Mocker.MockArraySbyte(), 494 | Mocker.MockArraySbyte() 495 | }, 496 | new sbyte[][]{ 497 | Mocker.MockArraySbyte(), 498 | Mocker.MockArraySbyte() 499 | } 500 | }; 501 | var arrIns2 = new short[][][] { 502 | new short[][]{ 503 | Mocker.MockArrayShort(), 504 | Mocker.MockArrayShort(), 505 | Mocker.MockArrayShort() 506 | }, 507 | new short[][]{ 508 | Mocker.MockArrayShort(), 509 | Mocker.MockArrayShort() 510 | } 511 | }; 512 | var arrIns3 = new int[][][] { 513 | new int[][]{ 514 | Mocker.MockArrayInt(), 515 | Mocker.MockArrayInt(), 516 | Mocker.MockArrayInt() 517 | }, 518 | new int[][]{ 519 | Mocker.MockArrayInt(), 520 | Mocker.MockArrayInt() 521 | } 522 | }; 523 | var arrIns4 = new long[][][] { 524 | new long[][]{ 525 | Mocker.MockArrayLong(), 526 | Mocker.MockArrayLong(), 527 | Mocker.MockArrayLong() 528 | }, 529 | new long[][]{ 530 | Mocker.MockArrayLong(), 531 | Mocker.MockArrayLong() 532 | } 533 | }; 534 | var arrIns5 = new byte[][][] { 535 | new byte[][]{ 536 | Mocker.MockArrayByte(), 537 | Mocker.MockArrayByte(), 538 | Mocker.MockArrayByte() 539 | }, 540 | new byte[][]{ 541 | Mocker.MockArrayByte(), 542 | Mocker.MockArrayByte() 543 | } 544 | }; 545 | var arrIns6 = new ushort[][][] { 546 | new ushort[][]{ 547 | Mocker.MockArrayUShort(), 548 | Mocker.MockArrayUShort(), 549 | Mocker.MockArrayUShort() 550 | }, 551 | new ushort[][]{ 552 | Mocker.MockArrayUShort(), 553 | Mocker.MockArrayUShort() 554 | } 555 | }; 556 | var arrIns7 = new uint[][][] { 557 | new uint[][]{ 558 | Mocker.MockArrayUInt(), 559 | Mocker.MockArrayUInt(), 560 | Mocker.MockArrayUInt() 561 | }, 562 | new uint[][]{ 563 | Mocker.MockArrayUInt(), 564 | Mocker.MockArrayUInt() 565 | } 566 | }; 567 | var arrIns8 = new ulong[][][] { 568 | new ulong[][]{ 569 | Mocker.MockArrayULong(), 570 | Mocker.MockArrayULong(), 571 | Mocker.MockArrayULong() 572 | }, 573 | new ulong[][]{ 574 | Mocker.MockArrayULong(), 575 | Mocker.MockArrayULong() 576 | } 577 | }; 578 | var arrIns9 = new float[][][] { 579 | new float[][]{ 580 | Mocker.MockArrayFloat(), 581 | Mocker.MockArrayFloat(), 582 | Mocker.MockArrayFloat() 583 | }, 584 | new float[][]{ 585 | Mocker.MockArrayFloat(), 586 | Mocker.MockArrayFloat() 587 | } 588 | }; 589 | var arrIns10 = new double[][][] { 590 | new double[][]{ 591 | Mocker.MockArrayDouble(), 592 | Mocker.MockArrayDouble(), 593 | Mocker.MockArrayDouble() 594 | }, 595 | new double[][]{ 596 | Mocker.MockArrayDouble(), 597 | Mocker.MockArrayDouble() 598 | } 599 | }; 600 | var arrIns11 = new decimal[][][] { 601 | new decimal[][]{ 602 | Mocker.MockArrayDecimal(), 603 | Mocker.MockArrayDecimal(), 604 | Mocker.MockArrayDecimal() 605 | }, 606 | new decimal[][]{ 607 | Mocker.MockArrayDecimal(), 608 | Mocker.MockArrayDecimal() 609 | } 610 | }; 611 | var arrIns12 = new GenderEnum[][][] { 612 | new GenderEnum[][]{ 613 | Mocker.MockArrayGenderEnum(), 614 | Mocker.MockArrayGenderEnum() 615 | }, 616 | new GenderEnum[][]{ 617 | Mocker.MockArrayGenderEnum(), 618 | Mocker.MockArrayGenderEnum(), 619 | Mocker.MockArrayGenderEnum() 620 | } 621 | }; 622 | var arrIns13 = new bool[][][] { 623 | new bool[][]{ 624 | Mocker.MockArrayBoolean(), 625 | Mocker.MockArrayBoolean(), 626 | Mocker.MockArrayBoolean() 627 | }, 628 | new bool[][]{ 629 | Mocker.MockArrayBoolean(), 630 | Mocker.MockArrayBoolean() 631 | } 632 | }; 633 | var arrIns14 = new char[][][] { 634 | new char[][]{ 635 | Mocker.MockArrayChar(), 636 | Mocker.MockArrayChar(), 637 | Mocker.MockArrayChar() 638 | }, 639 | new char[][]{ 640 | Mocker.MockArrayChar(), 641 | Mocker.MockArrayChar() 642 | } 643 | }; 644 | var arrIns15 = new string[][][] { 645 | new string[][]{ 646 | Mocker.MockArrayStr(), 647 | Mocker.MockArrayStr(), 648 | Mocker.MockArrayStr() 649 | }, 650 | new string[][]{ 651 | Mocker.MockArrayStr(), 652 | Mocker.MockArrayStr() 653 | } 654 | }; 655 | 656 | 657 | var arrIns0_Clone = CloneOperator.Clone(arrIns0); 658 | var arrIns1_Clone = CloneOperator.Clone(arrIns1); 659 | var arrIns2_Clone = CloneOperator.Clone(arrIns2); 660 | var arrIns3_Clone = CloneOperator.Clone(arrIns3); 661 | var arrIns4_Clone = CloneOperator.Clone(arrIns4); 662 | var arrIns5_Clone = CloneOperator.Clone(arrIns5); 663 | var arrIns6_Clone = CloneOperator.Clone(arrIns6); 664 | var arrIns7_Clone = CloneOperator.Clone(arrIns7); 665 | var arrIns8_Clone = CloneOperator.Clone(arrIns8); 666 | var arrIns9_Clone = CloneOperator.Clone(arrIns9); 667 | var arrIns10_Clone = CloneOperator.Clone(arrIns10); 668 | var arrIns11_Clone = CloneOperator.Clone(arrIns11); 669 | var arrIns12_Clone = CloneOperator.Clone(arrIns12); 670 | var arrIns13_Clone = CloneOperator.Clone(arrIns13); 671 | var arrIns14_Clone = CloneOperator.Clone(arrIns14); 672 | var arrIns15_Clone = CloneOperator.Clone(arrIns15); 673 | 674 | Assert.Equal(arrIns0_Clone, arrIns0); 675 | Assert.Equal(arrIns1_Clone, arrIns1); 676 | Assert.Equal(arrIns2_Clone, arrIns2); 677 | Assert.Equal(arrIns3_Clone, arrIns3); 678 | Assert.Equal(arrIns4_Clone, arrIns4); 679 | Assert.Equal(arrIns5_Clone, arrIns5); 680 | Assert.Equal(arrIns6_Clone, arrIns6); 681 | Assert.Equal(arrIns7_Clone, arrIns7); 682 | Assert.Equal(arrIns8_Clone, arrIns8); 683 | Assert.Equal(arrIns9_Clone, arrIns9); 684 | Assert.Equal(arrIns10_Clone, arrIns10); 685 | Assert.Equal(arrIns11_Clone, arrIns11); 686 | Assert.Equal(arrIns12_Clone, arrIns12); 687 | Assert.Equal(arrIns13_Clone, arrIns13); 688 | Assert.Equal(arrIns14_Clone, arrIns14); 689 | Assert.Equal(arrIns15_Clone, arrIns15); 690 | } 691 | 692 | /// 693 | /// 锯齿+复杂: object[][][] 694 | /// 695 | [Fact] 696 | public void CloneJaggedWithComplex() 697 | { 698 | Member[][][] arrIns0 = null; 699 | // var arrIns1 = new object[][][] { 700 | // new Member[][] { 701 | // Mocker.MockArrayMember(), 702 | // Mocker.MockArrayMember(), 703 | // Mocker.MockArrayMember() 704 | // }, 705 | // new Member[][] { 706 | // Mocker.MockArrayMember() 707 | // }, 708 | // new Member[][] { 709 | // Mocker.MockArrayMember(), 710 | // Mocker.MockArrayMember() 711 | // } 712 | // }; 713 | var arrIns2 = new Member[][][] { 714 | new Member[][] { 715 | Mocker.MockArrayMember(), 716 | Mocker.MockArrayMember(), 717 | Mocker.MockArrayMember() 718 | }, 719 | new Member[][] { 720 | Mocker.MockArrayMember() 721 | }, 722 | new Member[][] { 723 | Mocker.MockArrayMember(), 724 | Mocker.MockArrayMember() 725 | } 726 | }; 727 | var arrIns3 = new Dictionary[][][] { 728 | new Dictionary[][] { 729 | Mocker.MockArrayDict(), 730 | Mocker.MockArrayDict(), 731 | Mocker.MockArrayDict() 732 | }, 733 | new Dictionary[][] { 734 | Mocker.MockArrayDict() 735 | }, 736 | new Dictionary[][] { 737 | Mocker.MockArrayDict(), 738 | Mocker.MockArrayDict() 739 | } 740 | }; 741 | var arrIns4 = new List[][][] { 742 | new List[][] { 743 | Mocker.MockArrayListInt(), 744 | Mocker.MockArrayListInt(), 745 | Mocker.MockArrayListInt() 746 | }, 747 | new List[][] { 748 | Mocker.MockArrayListInt() 749 | }, 750 | new List[][] { 751 | Mocker.MockArrayListInt(), 752 | Mocker.MockArrayListInt() 753 | } 754 | }; 755 | // var arrIns5 = new dynamic[][][] { 756 | // new Member[][] { 757 | // Mocker.MockArrayMember(), 758 | // Mocker.MockArrayMember(), 759 | // Mocker.MockArrayMember() 760 | // }, 761 | // new Member[][] { 762 | // Mocker.MockArrayMember() 763 | // }, 764 | // new Member[][] { 765 | // Mocker.MockArrayMember(), 766 | // Mocker.MockArrayMember() 767 | // } 768 | // }; 769 | 770 | 771 | var arrIns0_Clone = CloneOperator.Clone(arrIns0); 772 | // var arrIns1_Clone = CloneOperator.Clone(arrIns1); 773 | var arrIns2_Clone = CloneOperator.Clone(arrIns2); 774 | var arrIns3_Clone = CloneOperator.Clone(arrIns3); 775 | var arrIns4_Clone = CloneOperator.Clone(arrIns4); 776 | // var arrIns5_Clone = CloneOperator.Clone(arrIns5); 777 | 778 | 779 | Assert.Null(arrIns0_Clone); 780 | Assert.Equal(arrIns0_Clone, arrIns0); 781 | 782 | // Assert.Equal(arrIns1_Clone, arrIns1); 783 | 784 | Assert.NotNull(arrIns2_Clone); 785 | Assert.NotSame(arrIns2_Clone, arrIns2); 786 | Assert.True(arrIns2_Clone.Length == arrIns2.Length); 787 | Assert.Equal(arrIns2_Clone, arrIns2, new Member2JaggedArrayEqualityComparer()); 788 | 789 | Assert.NotNull(arrIns3_Clone); 790 | Assert.NotSame(arrIns3_Clone, arrIns3); 791 | Assert.True(arrIns3_Clone.Length == arrIns3.Length); 792 | Assert.Equal(arrIns3_Clone, arrIns3, new Dict2JaggedArrayEqualityComparer()); 793 | 794 | Assert.NotNull(arrIns4_Clone); 795 | Assert.NotSame(arrIns4_Clone, arrIns4); 796 | Assert.True(arrIns4_Clone.Length == arrIns4.Length); 797 | Assert.Equal(arrIns4_Clone, arrIns4, new List2JaggedArrayEqualityComparer()); 798 | 799 | // Assert.Equal(arrIns5_Clone, arrIns5); 800 | } 801 | } 802 | } --------------------------------------------------------------------------------