继承和转换错误(通用接口)
Inheritance and Casting error (generic interfaces)
如何重构我的代码以消除在指定点发生的运行时错误?
DataSeries<SimpleDataPoint>
需要能够以某种方式转换回 IDataSeries<IDataPoint>
我试过使用两个接口的继承,像这样:
public class DataSeries<TDataPoint> : IDataSeries<TDataPoint>, IDataSeries<IDataPoint>
但收到编译器错误:
'DataSeries<TDataPoint>'
cannot implement both
'IDataSeries<TDataPoint>'
and
'IDataSeries<IDataPoint>'
because they may unify for some type parameter substitutions
使用协变似乎不是一个选项,因为我无法使接口协变或逆变。
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
可能会失败。也就是说,运行时无法保证您所做的是类型安全的,因此它会抛出一个异常来告诉您这一点。仅当您的接口被标记为协变或逆变时,运行时才能保证这些操作中的 一个 是安全的。它被标记为两者都不是,所以它不是类型安全的,所以它不能完成。
如果你打算绕过类型安全,你可以创建一个不安全的代理:
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
转换为基本类型,还要转换为其他类型,则必须向不安全代理添加更多类型转换,从而失去更多类型安全性。选择权在你。
所以我的问题让我想到了代码味道,以及 "What am I really trying to achieve?"
好吧,这就是我想要实现的目标:我想将 DataSeries<TDataPoint>
转换为 IReadOnlyDataSeries<IDataPoint>
仅当我将它作为输入传递给处理的 class来自 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 { }
}
如何重构我的代码以消除在指定点发生的运行时错误?
DataSeries<SimpleDataPoint>
需要能够以某种方式转换回 IDataSeries<IDataPoint>
我试过使用两个接口的继承,像这样:
public class DataSeries<TDataPoint> : IDataSeries<TDataPoint>, IDataSeries<IDataPoint>
但收到编译器错误:
'DataSeries<TDataPoint>'
cannot implement both
'IDataSeries<TDataPoint>'
and
'IDataSeries<IDataPoint>'
because they may unify for some type parameter substitutions
使用协变似乎不是一个选项,因为我无法使接口协变或逆变。
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
可能会失败。也就是说,运行时无法保证您所做的是类型安全的,因此它会抛出一个异常来告诉您这一点。仅当您的接口被标记为协变或逆变时,运行时才能保证这些操作中的 一个 是安全的。它被标记为两者都不是,所以它不是类型安全的,所以它不能完成。
如果你打算绕过类型安全,你可以创建一个不安全的代理:
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
转换为基本类型,还要转换为其他类型,则必须向不安全代理添加更多类型转换,从而失去更多类型安全性。选择权在你。
所以我的问题让我想到了代码味道,以及 "What am I really trying to achieve?"
好吧,这就是我想要实现的目标:我想将 DataSeries<TDataPoint>
转换为 IReadOnlyDataSeries<IDataPoint>
仅当我将它作为输入传递给处理的 class来自 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 { }
}