填充摘要中没有 setter 的字段 class

Populating a field that has no setter in abstract class

我正在单元测试中使用 class EventRecordWrittenEventArgs

public sealed class EventRecordWrittenEventArgs : EventArgs {
  public EventRecord EventRecord {get;}
  public Exception EventException {get;}
}

我想用我自己的自定义数据填充 EventExceptionEventRecord 字段,因此我创建了我自己的上述 class 版本,名为 EventRecordWrittenEventArgsAdaptor除了每个字段的 getter 之外,还有一个 setter。

问题是,EventRecord 是一个没有 setter 的抽象 class,这意味着我无法填充自己的自定义 EventRecord 对象(最终我的目标):

public abstract class EventRecord : IDisposable {
  public abstract Guid? ProviderId {get;}
  public abstract string TaskDisplayName {get;}
  public abstract string OpcodeDispayName {get;}
  ...
}

我的第一个想法是创建一个从 EventRecord 派生的 EventRecordAdaptor class 并给它每个字段一个 setter 但这不起作用:

public class EventRecordAdaptor : EventRecord {
  public override Guid? ProviderId {get;set;} // "cannot override because EventRecord.Guid does not have an overrideable set accessor"
}

我应该如何创建我自己的 EventRecord 对象,我可以随心所欲地填充它?需要明确的是,我的最终目标是能够用我想要的任何东西填充 EventRecordEventException 以进行单元测试。确定有办法填充此字段吗?

我不确定您的项目限制。如果您可以对抽象 class 进行以下更改,则可以将 GUID 传递给 EventRecord ,如下所示。

public abstract class EventRecord : IDisposable
{
  private readonly Guid _id;

  public EventRecord(Guid id)
   {
     _id = id;
   }

  public virtual Guid? ProviderId { get => _id; }
  public abstract string TaskDisplayName { get; }
 ......
 }

public class EventRecordAdaptor : EventRecord
{
  public EventRecordAdaptor(Guid id) : base(id)
  {
  }
    public override Guid? ProviderId => base.ProviderId;
    ...........
}

基于kirk-woll的评论:

public class EventRecordAdaptor : EventRecord
{
    public EventRecordAdaptor(Guid? id)
    {
        _activityId = id;
    }

    private Guid? _activityId;

    public override Guid? ActivityId => _activityId;

    ...

}

构造函数是内部的,所以基本上不能直接使用EventRecord。这在使用外部 API 时很常见。在这种情况下,您的代码应该 使用本机 .NET 类型,而是将其包装在您可以模拟的接口中。有许多 Nuget 库,人们在其中为各种 .NET 类 创建了代理,而 EventRecord 的代理可能已经存在,您可以使用。如果没有,自己编写也很简单。

// this is the interface your code will use *instead of* EventRecord
// that exposes the properties and methods your code needs
public interface IEventRecord
{
    public Guid? ProviderId {get;}
    public string TaskDisplayName {get;}
    public string OpcodeDispayName {get;}
}

// this is the concrete type you'll use in your production code
// to turn .NET's EventRecord into an IEventRecord
public EventRecordProxy : IEventRecord
{ 
    private EventRecord _eventRecord;

    public Guid? ProviderId =>  _eventRecord.ProviderId;
    public string TaskDisplayName => _eventRecord.TaskDisplayName;
    public string OpcodeDisplayName => _eventRecord.OpcodeDisplayName;

    public EventRecordProxy(EventRecord er)
    {
        _eventRecord = er;
    }
}

// this is the concrete type you'll use in your tests, with accessible properties
// (or if you're using a mocking framework, you can mock IEventRecord directly.)
public TestEventRecordProxy : IEventRecord
{
    public Guid? ProviderId { get; set; }
    public string TaskDisplayName { get; set}
    public string OpcodeDispayName { get; set;}
}

是的,一开始这很麻烦,但总的来说这是一种更加灵活和模块化的设计。您不再受限于 EventRecord 的细节。如果您决定在其他地方获取该数据,或将多个对象组合在一起,您现在可以轻松地做到这一点。