You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C# Record实现带可空成员的接口时的访问器冲突问题及解决

用C# Record实现IEmailModel接口的问题与解决方法

问题背景

我定义了如下接口:

public interface IEmailModel
{
    EmailAddressDto? Receiver { get; set; }
}

尝试用Record实现该接口:

public record EmailModelSendConfirmationToUserAndSuperadmin(EmailAddressDto? Receiver = null) : IEmailModel;

此实现无报错。但当实现另一个Record时:

public record EmailModelSendConfirmationEmailCodeToUser(string link, EmailAddressDto? Receiver = null) : IEmailModel;

出现错误:

'EmailModelSendConfirmationEmailCodeToUser' does not implement interface member 'IEmailModel.Receiver.set'.
'EmailModelSendConfirmationEmailCodeToUser.Receiver.init' cannot implement 'IEmailModel.Receiver.set'.

我将接口修改为:

public interface IEmailModel
{
    EmailAddressDto? Receiver { get; init; }
}

此时第二个Record无报错,但第一个Record出现错误:

'EmailModelSendConfirmationToUserAndSuperadmin' does not implement interface member 'IEmailModel.Receiver.init'.
'EmailModelSendConfirmationToUserAndSuperadmin.Receiver.set' cannot implement 'IEmailModel.Receiver.init'.

请问为何会出现这种情况,如何用Record正确实现该接口?

原因分析

这是因为C#编译器会根据record的主构造函数参数数量和默认值自动推断属性的setter类型:

  • 当record的主构造函数只有一个带默认值的参数(如第一个EmailModelSendConfirmationToUserAndSuperadmin),编译器会自动生成带set的属性,允许对象初始化后修改属性。
  • 当record的主构造函数有多个参数(即使其中一个带默认值,如第二个EmailModelSendConfirmationEmailCodeToUser的link和Receiver),编译器会生成带init的属性,这类record通常被设计为不可变对象,仅允许在初始化阶段设置属性。

接口的get; set;要求实现类提供可读写属性,get; init;要求实现类提供仅初始化阶段可写的属性,两者无法互相兼容,因此会出现互相报错的情况。

正确实现方式

有两种可靠的解决方法:

方法1:显式指定属性的setter类型

在record中显式定义接口的属性,明确指定setter类型,确保和接口定义一致:

如果接口使用get; set;:

public interface IEmailModel
{
    EmailAddressDto? Receiver { get; set; }
}

// 第一个record可保持自动生成(编译器已生成带set的属性)
public record EmailModelSendConfirmationToUserAndSuperadmin(EmailAddressDto? Receiver = null) : IEmailModel;

// 第二个record显式实现带set的属性
public record EmailModelSendConfirmationEmailCodeToUser(string link) : IEmailModel
{
    public EmailAddressDto? Receiver { get; set; } = null;
}

如果接口使用get; init;:

public interface IEmailModel
{
    EmailAddressDto? Receiver { get; init; }
}

// 第一个record显式实现带init的属性
public record EmailModelSendConfirmationToUserAndSuperadmin : IEmailModel
{
    public EmailAddressDto? Receiver { get; init; } = null;
}

// 第二个record保持自动生成即可(编译器已生成带init的属性)
public record EmailModelSendConfirmationEmailCodeToUser(string link, EmailAddressDto? Receiver = null) : IEmailModel;

方法2:统一显式实现接口

不管接口定义的是set还是init,所有record都显式定义Receiver属性,避免编译器自动推断带来的差异:

public interface IEmailModel
{
    EmailAddressDto? Receiver { get; set; } // 或改为get; init;
}

public record EmailModelSendConfirmationToUserAndSuperadmin : IEmailModel
{
    public EmailAddressDto? Receiver { get; set; } = null; // 对应接口的set,或改成init对应接口的init
}

public record EmailModelSendConfirmationEmailCodeToUser(string link) : IEmailModel
{
    public EmailAddressDto? Receiver { get; set; } = null; // 同上
}

内容的提问来源于stack exchange,提问作者Oleg Sh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 23:12:34