轻松实现深度Clone | Source Generators方式

前言

在开发中,我们经常需要创建某个类型实例的副本。

常用的方式,是继承ICloneable接口,然后自行实现Clone(),这会耗费一定的开发时间;或者使用序列化/反序列化方式变相实现,但是性能不高。

现在,可以尝试用Source Generators实现。

实现思路

首先,需要Clone的类必须声明一个特定的CloneableAttribute,这样Source Generators才知道为谁实现Clone方法。

然后,Source Generators遍历该类型的所有属性,为其编写属性赋值代码。

如果属性本身也是Cloneable类型,那就调用属性对应类型的Clone方法,实现深度克隆。

具体代码

1.添加CloneableAttribute

向待编译项目加入CloneableAttribute代码:

const string cloneableAttributeText = @"using System;

namespace CloneableDemo
{
    public sealed class CloneableAttribute : Attribute
    {
        public CloneableAttribute()
        {
        }
    }
}
";

context.AddSource("CloneableAttribute", SourceText.From(cloneableAttributeText, Encoding.UTF8));

2.遍历CloneableAttribute声明类

找到声明了CloneableAttribute的所有类型:

var cloneableAttribute = compilation.GetTypeByMetadataName("CloneableDemo.CloneableAttribute");
foreach (var classSymbol in classSymbols)
{
    if (!classSymbol.TryGetAttribute(cloneableAttribute, out var attributes))
        continue;

    context.AddSource($"{classSymbol.Name}_clone.cs", SourceText.From(CreateCloneCode(classSymbol), Encoding.UTF8));
}

3.生成Clone代码

遍历属性,生成Clone方法:

private string CreateCloneableCode(INamedTypeSymbol classSymbol)
        {
            string namespaceName = classSymbol.ContainingNamespace.ToDisplayString();
            var propertyNames = classSymbol.GetMembers().OfType<IPropertySymbol>();
            var codes = new StringBuilder();
            foreach (var propertyName in propertyNames)
            {
                if (isCloneable(propertyName))
                {
                    codes.AppendLine($@"                {propertyName} = obj.{propertyName}?.Clone(),");
                }
                else
                {
                    codes.AppendLine($@"                {propertyName} = obj.{propertyName},");
                }
            }

            return $@"using System.Collections.Generic;

namespace {namespaceName}
{{
    public static class {classSymbol.Name}Extentions
    {{
        public static {classSymbol.Name} Clone(this {classSymbol.Name} obj)
        {{
            return new {classSymbol.Name}
            {{
 {codes.ToString()}
            }};
        }}
    }}
}}";
        }

4.使用

现在,就可以在目标项目中使用Clone方法了:

[Cloneable]
public class Class1
{
    public string A { get; set; }
    public Class2 B { get; set; }
}

[Cloneable]
public class Class2
{
    public string A { get; set; }
}

var obj = new Class2()
{
    A = "My IO",
};
var deep = new Class1()
{
    A = "My IO",
    B = obj
};
var clone = deep.Clone();

结论

有了Source Generators,可以让编译器帮我们自动实现Clone方法,既节约了开发时间,又保证了性能!

如果你觉得这篇文章对你有所启发,请关注我的个人公众号”My IO“

发表评论

登录后才能评论
网站客服
网站客服
申请收录 侵权处理
分享本页
返回顶部