1. SingletonPattern

入门示例

定义单例模式,在需要定义的类上加上[SingletonPattern]特性,并保证类加上partial修饰符

[SingletonPattern]
public partial class Service1
{
    public void SayHello(string name)
    {
        Console.WriteLine($"Hello,{name}");
    }
}

上面的代码,因为定义了[SingletonPattern]特性,SourceGenerator会自动帮我们生成以下代码

partial class Service1
{
    private static readonly Lazy<Service1> LazyInstance = new Lazy<Service1>(() => new Service1(), true);

    private Service1() { }

    public static Service1 Instance => LazyInstance.Value;
}

使用的代码

Service1.Instance.SayHello("Zhang");

自定义单例属性名称

可以在[SingletonPattern]里面加上InstancePropertyName,用于指定单例属性的名称。

[SingletonPattern(InstancePropertyName = "Default")]
public partial class Service2
{
    public void SayHello(string name)
    {
        Console.WriteLine($"Hello,{name}");
    }
}

使用的代码

Service2.Default.SayHello("Zhang");

2. AutoConstructor

入门示例

需要在自动生成构造函数的类上面加上[AutoConstructor], 并确保类加上partial修饰符,这样就会生成一个包含类内部所有字段和属性的构造函数。

[AutoConstructor]
public partial class Demo1
{
    private string strValue;
    private int intValue;
}

应用了[AutoConstructor]后,SourceGenerator会自动生成以下代码片段

partial class Demo1
{
    public Demo1(string strValue, int intValue)
    {
        this.strValue = strValue;
        this.intValue = intValue;
    }
}

可以看到生成的代码相当于我们手写的代码,并且自动把字段赋值成构造函数传入的值了。

使用的代码如下

var demo = new Demo1("val1", 1);

使用属性

对于自动实现的属性,AutoConstructor同样适用。这里要注意的是必须是自动实现的属性,如果属性包含了代码定义,则会忽略改属性。

[AutoConstructor]
public partial class Demo2
{
    public string StrValue { get; set; }
    public int IntValue { get; set; }
}

使用的代码

var demo = new Demo2("val2", 2);

NullCheck


```csharp
[AutoConstructor(NullCheck = true)]
public partial class Demo3
{
    private string strValue;
    private int intValue;
}

上述代码对应生成的代码如下

partial class Demo3
{
    public Demo3(string strValue, int intValue)
    {
        this.strValue = strValue ?? throw new System.ArgumentNullException(nameof(strValue));
        this.intValue = intValue;
    }
}

当使用以下代码调用时,会抛出System.ArgumentNullException的异常

var demo = new Demo3(null, 1);

忽略字段

如果想要指定某个字段或者属性不通过构造函数赋值,那么可以在该字段或属性上面使用AutoConstructorIgnore特性

[AutoConstructor]
public partial class Demo4
{
    private string strValue;
    [AutoConstructorIgnore]
    private int intValue;
}

上述代码生成的构造函数就会忽略intValue字段的赋值。

partial class Demo4
{
    public Demo4(string strValue)
    {
        this.strValue = strValue;
    }
}

初始化函数

允许我们为每个类定义一个无参的初始化函数,我们需要在这个函数上加上[AutoConstructorInitialize]特性,那么生成的构造函数会自动帮我们去调用这个初始化函数。

例如以下代码
[AutoConstructor]
public partial class Demo5
{
    private string strValue;
    private int intValue;

    [AutoConstructorInitialize]
    private void MyInitLogic()
    {
        Console.WriteLine("instance is creating.");
    }
}

生成的构造函数如下

partial class Demo5
{
    public Demo5(string strValue, int intValue)
    {
        this.strValue = strValue;
        this.intValue = intValue;
        this.MyInitLogic();
    }
}

一般情况下,[AutoConstructorInitialize]都会结合[AutoConstructorIgnore],完成自定义的初始化逻辑。

依赖注入

在使用依赖注入的场景,使用AutoConstructor可以帮助我们减少写构造函数的麻烦。例如以下的代码,如果可以从Dependency Injection容器中获取到IService1IService2的实例的话,那么从容器中获取Demo6Service则可以正常工作。

public interface IService1
{
    void Action1();
}
public interface IService2
{
    void Action2();
}
[AutoConstructor]
public partial class Demo6Service
{ 
    private readonly IService1 service1;
    private readonly IService2 service2;
    public void Action()
    {
        service1.Action1();
        service2.Action2();
    }
}

继承


```csharp
[AutoConstructor]
public partial class Demo7Parent
{
    private string strValue;
}
[AutoConstructor]
public partial class Demo7Child: Demo7Parent
{
    private int intValue;
}

自动生成的代码如下

partial class Demo7Parent
{
    public Demo7Parent(string strValue)
    {
        this.strValue = strValue;
    }
}
partial class Demo7Child
{
    public Demo7Child(string strValue, int intValue)
        : base(strValue: strValue)
    {
        this.intValue = intValue;
    }
}

3. Mapper

入门示例

假如我们有两个User的类,结构相似,我们需要把其中的一个对象转换成另一个对象。

public record UserDto
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
}
public record UserEntity
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
}

只需要在需要转换的类上面定义[Mapper(typeof(UserEntity), typeof(UserDto))],当定义了以上Mapper特性后,SourceGenerator会自动给UserEntity增加一个To的泛型方法。使用这个泛型方法我们可以把UserEntity转换为UserDto 对象。

[Mapper(typeof(UserEntity), typeof(UserDto))]
internal class Program
{
    static void Main(string[] args)
    {
        var user = new UserEntity { Age = 10, Name = "zhangsan", Id = Guid.NewGuid() };
        var userDto = user.To<UserDto>();
        Console.WriteLine(userDto);
    }
}

以上代码生成的核心代码如下

private static global::Mapper.Demo.UserDto ToMapper_Demo_UserDto(this global::Mapper.Demo.UserEntity source)
{
    if (source == null) return default;
    return new global::Mapper.Demo.UserDto
    {
        Id = source.Id,
        Name = source.Name,
        Age = source.Age,
    };
}
public static T To<T>(this global::Mapper.Demo.UserEntity source) where T : new()
{
    if (source == null) return default;
    if (typeof(T) == typeof(global::Mapper.Demo.UserDto))
    {
        return (T)(object)ToMapper_Demo_UserDto(source);
    }
    throw new NotSupportedException($"Can not convert '{typeof(global::Mapper.Demo.UserEntity)}' to '{typeof(T)}'.");
}

单个对象存在嵌套对象

[Mapper(typeof(UserEntity), typeof(UserDto))]
internal class Program
{
    static void Main(string[] args)
    {
        var user = new UserEntity { Age = 10, Name = "zhangsan", City = new CityEntity { Name = "Xi'an", Province = "Shannxi" };
        var userDto = user.To<UserDto>();
        Console.WriteLine(userDto);
    }
}
public record UserEntity
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
    public CityEntity City { get; set; }
}
public record CityEntity
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    public string Province { get; set; }
}
public record UserDto
{
    public string Name { get; set; }
    public int Age { get; set; }
    public CityDto City { get; set; }
}
public record CityDto
{
    public string Name { get; set; }
    public string Province { get; set; }
}

上面的代码运行输出如下,可以看见City属性也被正确的填充了,因为我们定义了[Mapper(typeof(UserEntity), typeof(UserDto))],这样会自动尝试帮我们映射City属性,即使两个的类型不同。

UserDto { Name = zhangsan, Age = 10, City = CityDto { Name = xi'an, Province = shannxi } }

生成的核心代码如下

private static global::Mapper.Demo.UserDto ToMapper_Demo_UserDto(this global::Mapper.Demo.UserEntity source)
{
    if (source == null) return default;
    return new global::Mapper.Demo.UserDto
    {
        Name = source.Name,
        Age = source.Age,
        City = source.City == null ? default : new global::Mapper.Demo.CityDto
        {
            Name = source.City.Name,
            Province = source.City.Province,
        },
    };
}

导航属性

有时候,目标对象会展示源对象的一些属性,例如以下的例子

[Mapper(typeof(UserEntity), typeof(UserDto))]
internal class Program
{
    static void Main(string[] args)
    {
        var user = new UserEntity { Age = 10, Name = "zhangsan", City = new CityEntity { Name = "xi'an", Province = "shannxi" } };
        var userDto = user.To<UserDto>();
        Console.WriteLine(userDto);
    }
}
public record UserEntity
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
    public CityEntity City { get; set; }
}
public record CityEntity
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    public string Province { get; set; }
}
public record UserDto
{
    public string Name { get; set; }
    public int Age { get; set; }
    public Guid CityId { get; set; }
    public string CityName { get; set; }
    public string CityProvince { get; set; }
}

生成的核心代码如下

 private static global::Mapper.Demo.UserDto ToMapper_Demo_UserDto(this global::Mapper.Demo.UserEntity source)
{
    if (source == null) return default;
    return new global::Mapper.Demo.UserDto
    {
        Name = source.Name,
        Age = source.Age,
        CityId = source.City.Id,
        CityName = source.City.Name,
        CityProvince = source.City.Province,
    };
}

单个对象存在嵌套集合对象

[Mapper(typeof(UserEntity), typeof(UserDto))]
internal class Program
{
    static void Main(string[] args)
    {
        var user = new UserEntity { Age = 10, Name = "zhangsan", Cities =  new List<CityEntity> { new CityEntity { Name = "xi'an", Province = "shannxi" } } };
        var userDto = user.To<UserDto>();
        Console.WriteLine(userDto);
    }
}
public record UserEntity
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
    public List<CityEntity> Cities { get; set; }
}
public record CityEntity
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    public string Province { get; set; }
}
public record UserDto
{
    public string Name { get; set; }
    public int Age { get; set; }
    public CityDto[] Cities { get; set; }
}
public record CityDto
{
    public string Name { get; set; }
    public string Province { get; set; }
}

生成的核心代码如下

private static global::Mapper.Demo.UserDto ToMapper_Demo_UserDto(this global::Mapper.Demo.UserEntity source)
{
    if (source == null) return default;
    return new global::Mapper.Demo.UserDto
    {
        Name = source.Name,
        Age = source.Age,
        Cities = source.Cities == null ? default : source.Cities.Select(p => p == null ? default(global::Mapper.Demo.CityDto) : new global::Mapper.Demo.CityDto
        {
            Name = p.Name,
            Province = p.Province,
        }).ToArray(),
    };
}

转换集合对象

也可以一次性转换集合对象,例如下面的例子

[Mapper(typeof(UserEntity), typeof(UserDto))]
internal class Program2
{
    static void Main(string[] args)
    {
        var userList = new List<UserEntity>
        {
                new UserEntity { Age = 10, Name = "zhangsan", Id = Guid.NewGuid() }
        };
        var userDtoArray = userList.To<UserDto>().ToArray();
        Console.WriteLine(userDtoArray);
    }
}
public record UserDto
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
}
public record UserEntity
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
}

核心代码如下

public static IEnumerable<T> To<T>(this IEnumerable<global::Mapper.Demo.UserEntity> source) where T : new()
{
    if (typeof(T) == typeof(global::Mapper.Demo.UserDto))
    {
        return (IEnumerable<T>)source?.Select(p => p.ToMapper_Demo_UserDto());
    }
    throw new NotSupportedException($"Can not convert '{typeof(global::Mapper.Demo.UserEntity)}' to '{typeof(T)}'.");
}

IQueryable的转换

更新对象

更新字典

更新集合对象,CollectionUpdateMode

忽略字段,IgnoreProperties

自定义映射,CustomMappings

编译检查,CheckType

减少生成的代码,合理使用MapperType

4. AutoNotify

5. CodeException


Table of contents