簡體   English   中英

繼承和轉換錯誤(通用接口)

[英]Inheritance and Casting error (generic interfaces)

如何重構我的代碼以擺脫指示點發生的運行時錯誤?

DataSeries<SimpleDataPoint>需要能夠以某種方式轉換回IDataSeries<IDataPoint>

我嘗試過使用兩個接口的繼承,如下所示:

public class DataSeries<TDataPoint> : IDataSeries<TDataPoint>, IDataSeries<IDataPoint>但收到編譯器錯誤:

'DataSeries<TDataPoint>'無法實現這兩者

'IDataSeries<TDataPoint>'

'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> new DataSeries<SimpleDataPoint> 不是 IDataSeries<IDataPoint> ,因為調用IDataSeries<IDataPoint>.Value = new AnotherDataPoint()IDataPoint value = IDataSeries<IDataPointBase>.Value可能會失敗。 也就是說,運行時不能保證你所做的是類型安全的,所以它拋出一個異常來告訴你。 只有當您的接口被標記為協變或逆變時,運行時才能保證其中一個操作是安全的。 它既沒有標記,也沒有類型安全,所以無法完成。

如果您打算繞過類型安全,可以創建不安全的代理:

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 { }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM