继承和类型转换错误(泛型接口)

本文关键字:泛型接口 错误 类型转换 继承 | 更新日期: 2023-09-27 17:50:22

我如何重构我的代码以摆脱在指定点发生的运行时错误?

DataSeries<SimpleDataPoint>需要能够以某种方式转换回IDataSeries<IDataPoint>

我已经尝试使用两个接口的继承,像这样:

public class DataSeries<TDataPoint> : IDataSeries<TDataPoint>, IDataSeries<IDataPoint>但是收到编译错误:

'DataSeries<TDataPoint>'不能同时实现

'IDataSeries<TDataPoint>' and

'IDataSeries<IDataPoint>'因为它们可以统一用于某些类型参数替换

使用协方差似乎不是一个选择,因为我不能使接口协变或逆变。

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApplication1 {
class Program {
    [STAThread]
    static void Main(string[] args) {
        var source = new object();
        // compiles fine, but ...
        // runtime error here - cannot cast
        var ds = (IDataSeries<IDataPoint>)new DataSeries<SimpleDataPoint>(source);
        Console.ReadKey();
    }
}
public interface IDataPoint {
    int Index { get; set; }
    double Value { get; set; }
    DateTime TimeStampLocal { get; set; }
    IDataPoint Clone();
}
public sealed class SimpleDataPoint : IDataPoint {
    public int Index { get; set; }
    public double Value { get; set; }
    public DateTime TimeStampLocal { get; set; }
    public IDataPoint Clone() {
        return new SimpleDataPoint {
            Index = Index,
            Value = Value,
            TimeStampLocal = TimeStampLocal,
        };
    }
}
public interface IDataSeries<TDataPoint> where TDataPoint : class, IDataPoint {
    object Source { get; }
    int Count { get; }
    double GetValue(int index);
    DateTime GetTimeStampLocal(int index);
    TDataPoint GetDataPoint(int index);
    TDataPoint GetLastDataPoint();
    void Add(TDataPoint dataPoint);
    IDataSeries<TDataPoint> Branch(object source);
}
public class DataSeries<TDataPoint> : IDataSeries<TDataPoint> where TDataPoint : class, IDataPoint {
    readonly List<TDataPoint> _data = new List<TDataPoint>();
    public object Source {
        get;
        private set;
    }
    public DataSeries(object source) {
        Source = source;
    }
    public int Count {
        get { return _data.Count; }
    }
    public TDataPoint GetDataPoint(int index) {
        return _data[index];
    }
    public TDataPoint GetLastDataPoint() {
        return _data[_data.Count - 1];
    }
    public DateTime GetTimeStampLocal(int index) {
        return _data[index].TimeStampLocal;
    }
    public double GetValue(int index) {
        return _data[index].Value;
    }
    public void Add(TDataPoint dataPoint) {
        _data.Add(dataPoint);
    }
    public IDataSeries<TDataPoint> Branch(object source) {
        throw new NotImplementedException();
    }
}
}

继承和类型转换错误(泛型接口)

问题是new DataSeries<SimpleDataPoint> 不是 IDataSeries<IDataPoint>,因为调用IDataSeries<IDataPoint>.Value = new AnotherDataPoint()IDataPoint value = IDataSeries<IDataPointBase>.Value可能失败。也就是说,运行时不能保证你所做的是类型安全的,所以它抛出一个异常来告诉你这一点。只有当接口被标记为协变或逆变时,运行时才能保证这些操作中的一个是安全的。它被标记为both,所以它不是类型安全的,所以不能这样做。

如果你想绕过类型安全,你可以创建一个不安全的代理:

public class DataSeries<TDataPoint> : IDataSeries<TDataPoint>
    where TDataPoint : class, IDataPoint
{
    // ...
    public IDataSeries<IDataPoint> GetUnsafeProxy ()
    {
        return new UnsafeProxy(this);
    }
    private class UnsafeProxy : IDataSeries<IDataPoint>
    {
        private readonly DataSeries<TDataPoint> _owner;
        public UnsafeProxy (DataSeries<TDataPoint> owner)
        {
            _owner = owner;
        }
        public object Source
        {
            get { return _owner.Source; }
        }
        public int Count
        {
            get { return _owner.Count; }
        }
        public double GetValue (int index)
        {
            return _owner.GetValue(index);
        }
        public DateTime GetTimeStampLocal (int index)
        {
            return _owner.GetTimeStampLocal(index);
        }
        public IDataPoint GetDataPoint (int index)
        {
            return _owner.GetDataPoint(index);
        }
        public IDataPoint GetLastDataPoint ()
        {
            return _owner.GetLastDataPoint();
        }
        public void Add (IDataPoint dataPoint)
        {
            _owner.Add((TDataPoint)dataPoint);
        }
        public IDataSeries<IDataPoint> Branch (object source)
        {
            return (IDataSeries<IDataPoint>)_owner.Branch(source);
        }
    }

你可以这样使用这个代理:

IDataSeries<IDataPoint> ds = new DataSeries<SimpleDataPoint>(source).GetUnsafeProxy();

请注意,最后两个方法使用类型强制转换,因此调用它们是不安全的,如果类型不兼容,它们可能会抛出。如果您不仅希望将DataSeries强制转换为基类型,还希望将其转换为其他类型,那么您将不得不向不安全的代理添加更多类型强制转换,从而失去更多类型安全性。

所以我的问题让我思考代码气味,以及"我真正想要实现的是什么?"

好吧,这就是我想要实现的:我想将DataSeries<TDataPoint>转换为IReadOnlyDataSeries<IDataPoint>,只有当我将其作为输入传递给处理IReadonlyDataSeries<IDataPoint>对象只读数据的类时。

重要的改动如下:

// here's the covariant, read-only part of the interface declaration
public interface IReadOnlyDataSeries<out TDataPoint> where TDataPoint : class, IDataPoint {
    object Source { get; }
    int Count { get; }
    double GetValue(int index);
    DateTime GetTimeStampLocal(int index);
    TDataPoint GetDataPoint(int index);
    TDataPoint GetLastDataPoint();
}
// add a few bits to the read-write fully-typed interface, breaking covariance,
// but being able to implicitly cast to the covariant readonly version when needed
public interface IDataSeries<TDataPoint> : IReadOnlyDataSeries<TDataPoint> where TDataPoint : class, IDataPoint {
    void Add(TDataPoint dataPoint);
    IDataSeries<TDataPoint> Branch(object source);
}

以下是修改后代码的完整版本:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApplication1 {
class Program {
    [STAThread]
    static void Main(string[] args) {
        var source = new object();
        // implicit conversion works great!!
        // therefore I can achieve the goal of passing the fully-typed read-write dataseries
        // into objects that just want simple read-only data
        var inputSeries = new DataSeries<SimpleDataPoint>(source);
        // passing inputSeries into the constructor involves an implicit
        // conversion to IReadOnlyDataSeries<IDataPoint>
        var processor = new DataProcessor(inputSeries);
        Console.ReadKey();
    }
    public class DataProcessor {
        IReadOnlyDataSeries<IDataPoint> InputSeries;
        DataSeries<SimpleDataPoint> OutputSeries;
        public DataProcessor(IReadOnlyDataSeries<IDataPoint> inputSeries) {
            InputSeries = inputSeries;
            OutputSeries = new DataSeries<SimpleDataPoint>(this);
        }
    }
}
public interface IDataPoint {
    int Index { get; set; }
    double Value { get; set; }
    DateTime TimeStampLocal { get; set; }
    IDataPoint Clone();
}
public sealed class SimpleDataPoint : IDataPoint {
    public int Index { get; set; }
    public double Value { get; set; }
    public DateTime TimeStampLocal { get; set; }
    public IDataPoint Clone() {
        return new SimpleDataPoint {
            Index = Index,
            Value = Value,
            TimeStampLocal = TimeStampLocal,
        };
    }
}
// here's the covariant, read-only part of the interface declaration
public interface IReadOnlyDataSeries<out TDataPoint> where TDataPoint : class, IDataPoint {
    object Source { get; }
    int Count { get; }
    double GetValue(int index);
    DateTime GetTimeStampLocal(int index);
    TDataPoint GetDataPoint(int index);
    TDataPoint GetLastDataPoint();
}
// add a few bits to the read-write fully-typed interface, breaking covariance,
// but being able to implicitly cast to the covariant readonly version when needed
public interface IDataSeries<TDataPoint> : IReadOnlyDataSeries<TDataPoint> where TDataPoint : class, IDataPoint {
    void Add(TDataPoint dataPoint);
    IDataSeries<TDataPoint> Branch(object source);
}
public class DataSeries<TDataPoint> : IDataSeries<TDataPoint> where TDataPoint : class, IDataPoint {
    readonly List<TDataPoint> _data = new List<TDataPoint>();
    public object Source {
        get;
        private set;
    }
    public DataSeries(object source) {
        Source = source;
    }
    public int Count {
        get { return _data.Count; }
    }
    public TDataPoint GetDataPoint(int index) {
        return _data[index];
    }
    public TDataPoint GetLastDataPoint() {
        return _data[_data.Count - 1];
    }
    public DateTime GetTimeStampLocal(int index) {
        return _data[index].TimeStampLocal;
    }
    public double GetValue(int index) {
        return _data[index].Value;
    }
    public void Add(TDataPoint dataPoint) {
        _data.Add(dataPoint);
    }
    public IDataSeries<TDataPoint> Branch(object source) {
        throw new NotImplementedException();
    }
}
}

原始代码的最小大纲表明,可以通过在IDataSeries接口声明中使TDataPoint协变来解决这个问题:

using System;
namespace ConsoleApplication1
{
    class Program
    {
        [STAThread]
        static void Main(string[] args)
        {
            var ds = (IDataSeries<IDataPoint>)new DataSeries<SimpleDataPoint>();
            Console.ReadKey();
        }
    }
    public interface IDataPoint { }
    public sealed class SimpleDataPoint : IDataPoint { }
    public interface IDataSeries<out TDataPoint> where TDataPoint : class, IDataPoint { }
    public class DataSeries<TDataPoint> : IDataSeries<TDataPoint> where TDataPoint : class, IDataPoint { }
}