从基类返回派生类

问题描述 投票:0回答:1

我们正在开发一个内部 NuGet 包,其中包含

ServiceRes
ServiceRes<T>
类,可用于将服务层的响应包装到通用结构中。这是我们目前拥有的代码:

public class ServiceRes
{
    protected internal ServiceRes() { }

    public bool IsSuccess { get; set; }

    public string? Message { get; set; }

    public static ServiceRes Success()
    {
        return Success(message: null);
    }

    public static ServiceRes Success(string? message)
    {
        return new ServiceRes
        {
            IsSuccess = true,
            Message = message
        };
    }

    public static ServiceRes MapFrom<TPrev>(ServiceRes<TPrev> othr)
        where TPrev : class
    {
        return new ServiceRes
        {
            IsSuccess = othr.IsSuccess,
            Message = othr.Message
        };
    }
}

public class ServiceRes<T> : ServiceRes where T : class
{
    protected internal ServiceRes() { }

    public T? ResponseObj { get; set; }

    public static ServiceRes<T> Success(T responseObj)
    {
        return new ServiceRes<T>
        {
            IsSuccess = true,
            Message = null,
            ResponseObj = responseObj
        };
    }

    public static new ServiceRes<T> MapFrom<TPrev>(ServiceRes<TPrev> othr)
        where TPrev : class
    {
        return MapFrom(othr, null);
    }

    public static ServiceRes<T> MapFrom<TPrev>(ServiceRes<TPrev> othr, Func<TPrev?, T?>? responseObjMapper)
        where TPrev : class
    {
        return new ServiceRes<T>
        {
            IsSuccess = othr.IsSuccess,
            Message = othr.Message,
            ResponseObj = responseObjMapper?.Invoke(othr.ResponseObj)
        };
    }
}

我们有一个 .NET Core Web 应用程序项目,它引用我们的私有 NuGet 包。我们可以这样使用它:

public class ServiceResTest
{
    private class Obj1
    {
        public string? Name { get; set; }
    }

    private class Obj2
    {
        public string? Value { get; set; }
    }
    
    public void Test()
    {
        // base usage
        var x1 = ServiceRes.Success();
        var x2 = ServiceRes.Success(message: "User created successfully");
        var x3 = ServiceRes<Obj1>.Success(new Obj1());

        // mapping
        var x4 = ServiceRes.MapFrom(x3); // ServiceResult<Obj1> -> ServiceResult
        var x5 = ServiceRes<Obj2>.MapFrom(x3); // ServiceResult<Obj1> -> ServiceResult<Obj2> (ResponseObj = null)
        var x6 = ServiceRes<Obj2>.MapFrom(x3, x => new Obj2() // ServiceResult<Obj1> -> ServiceResult<Obj2>
        {
            Value = $"{x?.Name}_value"
        }); 
    }
}

但是,我们的 Web 应用程序中有一些高级用例需要扩展

BaseService<T>
类。我们希望能够将
ServiceResult<T>
映射到
ServiceResultExt<T>
,最好采用与现有
MapFrom
方法类似的方式。下面的评论指出了该过程失败的地方。

public class ServiceResExt<T> : ServiceRes<T> where T : class
{
    public int FailedLoginsCount { get; set; }

    public static ServiceResExt<T> SuccessExt(T responseObj, int failedLoginsCount)
    {
        var baseRes = ServiceRes<T>.Success(responseObj);
        // map ServiceRes<T> to ServiceResExt<T>
        var x1 = ServiceResExt<T>.MapFrom(baseRes); // x1 is of type ServiceRes<T>
        // we would like to assing parameter failedLoginsCount to property FailedLoginsCount 
        x1.FailedLoginsCount = failedLoginsCount;
        return x1;
    }
}

处理以下情况的最佳方法是什么:我们有一个扩展

ServiceRes<T>
(例如
ServiceResExt<T>
)的类,并且我们希望利用现有方法(例如
Success
),但将响应映射到
ServiceResExt<T> 
?派生类的映射逻辑是否可以放置在
ServiceRes<T>
类中,以便对于从它继承的所有类都是通用的(我们不希望任何人手动实例化
ServiceRes
类,因此需要
protected internal
构造函数)?
ServiceRes<T>
可以返回派生类的实例吗?

c# generics
1个回答
0
投票

您可以在映射时使用受保护的虚拟方法来创建正确的对象类型。

我会这样写:

public class ServiceRes
{
    protected ServiceRes(bool isSuccess, string message) 
        => ( IsSuccess, Message ) = ( isSuccess, message );

    public static ServiceRes Success => new (true, "");
    public static ServiceRes Fail(string message) => new(false, message);
    public bool IsSuccess { get; }
    public string Message { get; }
}

public class ServiceRes<T> : ServiceRes
    where T : class
{
    private readonly T responseObj;
    protected ServiceRes(T response, bool isSuccess, string message) : base(isSuccess, message)
        => responseObj = response;
    protected virtual ServiceRes<T2> Create<T2>(T2 v) where T2 : class => new(v, IsSuccess, Message);

    public T Response => IsSuccess ? responseObj : throw new InvalidOperationException();
    public new static ServiceRes<T> Success(T response)  => new(response, true,  "");
    public new static ServiceRes<T> Fail(string message) => new(null, false,message);
    public ServiceRes<T2> MapFrom<T2>(Func<T, T2> select) where T2 : class 
        => IsSuccess ? Create(select(Response)) : Create<T2>(null);
}

public class ServiceResExt<T> : ServiceRes<T>
    where T : class
{
    protected ServiceResExt(T value, bool isSuccess, string message, int failedLoginsCount) : base(value, isSuccess, message) 
        => FailedLoginsCount = failedLoginsCount;
    protected virtual ServiceResExt<T2> CreateExt<T2>(T2 v) where T2 : class
        => new(v, IsSuccess, Message, FailedLoginsCount);
    protected override ServiceRes<T2> Create<T2>(T2 v) => CreateExt(v);

    public int FailedLoginsCount { get; }
    public new static ServiceResExt<T> Success(T response, int failedLoginsCount) => new(response, true, "", failedLoginsCount);
    public new static ServiceResExt<T> Fail(string message, int failedLoginsCount) => new(null, false, message, failedLoginsCount);
    public new ServiceResExt<T2> MapFrom<T2>(Func<T, T2> select) where T2 : class 
        => IsSuccess ? CreateExt(select(Response)) : CreateExt<T2>(null);
}

映射

ServiceRes
基类是没有意义的,因此它缺少任何此类方法。

而且我不确定它是否达到了您在基础中拥有映射逻辑的目标

ServiceRes<T>
。但这不应该是那么多逻辑,您可以将
ServiceResExt.MapFrom
替换为
(ServiceResExt<T2>)base.MapFrom(select)
,即使如果添加进一步的扩展,这会不太安全。

但是无论引用的类型如何,它在映射之前和之后都应该保持相同的外部类型。由于隐藏了

MapFrom
方法,还返回相同的外部类型的引用。

您可能还需要添加一些

?
,我写的这个没有可为空的引用类型。我建议让这些类不可变,让公共设置器看起来很奇怪而且危险。

© www.soinside.com 2019 - 2024. All rights reserved.