尝试生成要应用于图像的过滤器的通用队列(在示例过滤器中使用OpenCVSharp.GaussianBlur
,但将其设置为通用,以便我可以插入我创建的任何自定义过滤器。)
我在使用C#generics时遇到了一些困难,并且intellisense正在显示:
无法从'GaussianBlur'转换为'IFilter
Intellisense建议更改以下行:
filters.Enqueue(filter);
通过强制转换到界面
filters.Enqueue((IFilter<IFilterParams>)filter);
但是,我的问题是为什么在具体类实现接口时需要强制转换,并且需要泛型定义,或者我误解了如何使用泛型声明类。
目前的实施代码如下:
public class FilterTest
{
private FilterCollection filters = new FilterCollection();
/* ... other irrelevant code ... */
public void ApplyFilters(ref Mat buffer)
{
var filter = new GaussianBlur(new GaussianBlurParams { KernelSize = new Size(6, 6) });
filters.Enqueue((IFilter<IFilterParams>)filter);
filters.Apply(ref buffer);
}
}
。 我正在扩展队列&lt;&gt; FilterCollection的类:
public class FilterCollection : Queue<IFilter<IFilterParams>>
{
public void Apply(ref Mat buffer)
{
while (Count > 0)
Dequeue().Apply(ref buffer);
}
}
和IFilter和IFilterParams的接口如下:
public interface IFilter<T> where T : IFilterParams
{
void Apply(ref Mat buffer);
}
public interface IFilterParams { }
然后是示例过滤器实现(在这种情况下或多或少只是一个包装器):
public class GaussianBlurParams : IFilterParams
{
public Size KernelSize = new Size(5, 5);
public double SigmaX = default(double);
public double SigmaY = default(double);
public BorderTypes BorderType = BorderTypes.Default;
}
public class GaussianBlur : IFilter<GaussianBlurParams>
{
private GaussianBlurParams p;
public GaussianBlur(GaussianBlurParams filterParams)
{
this.p = filterParams;
}
public void Apply(ref Mat buffer)
{
Cv2.GaussianBlur(buffer, buffer, p.KernelSize, p.SigmaX, p.SigmaY, p.BorderType);
}
}
所以给出:
GaussianBlur
实施IFilter<GaussianBlurParams>
IFilter<T> where T : IFilterParams
GaussianBlurParams
实施IFilterParams
是否正在构建修复此问题的唯一方法,或者编写通用类/接口的结构是否有问题?
答案 0 :(得分:3)
您的代码有多个方面会被纠缠在一起,并且这些方面总是使设计不够理想。乍一看,这可能看起来像一个协方差问题,但仔细看看情况并非如此。这里的两个主要方面是通用约束和接口。为了理解我的意思,让我们来看看这两种语言元素的一些好处。
通用约束
虽然泛型使您能够以类型安全的方式对多种类型使用模式的实现,但是您无法在泛型类中直接操作类型为T
的对象。您无法创建实例,您不能依赖实例作为引用或值类型(尝试与null
进行比较以查看其含义)并且您无法访问除{{1}中定义的成员之外的任何其他成员}}。这就是为什么你可以使用泛型约束来允许泛型类中的代码可以使用System.Object
类型的对象执行其他操作,比如创建实例(使用T
约束)或访问其他成员(通过约束{ {1}}到某种类型和/或一个或多个接口。)
<强>接口强>
接口提供合同保证,实施者将拥有一组已定义的成员。此保证针对接口的使用者,而不是实施者。这意味着您不使用接口来强制其实现者提供一些对接口的使用者没有任何价值的成员。
这意味着您的情况
问题的关键在于代码的这一部分:
new()
特别是:
您定义了通用约束T
,但public interface IFilter<T> where T : IFilterParams
{
void Apply(ref Mat buffer);
}
public interface IFilterParams { }
未提供任何成员。此约束不会为您的设计增加任何价值。您将实施者限制为某个where T : IFilterParams
,但是您没有从中获得任何东西,因为您无法对IFilterParams
的实例做任何事情,如果没有约束,您就无法做到。
更进一步,您根本不需要接口是通用的。您甚至不在界面提供的唯一成员中使用T
。只要涉及到界面的保证,你就可以做得很好。
看一下T
T
的实现,很明显你只在构造函数中使用GaussianBlur
,而构造函数不是接口的一部分。因此,您仅使用接口约束来限制实现者使用实现IFilter<T>
的{{1}}类。这甚至不是真正的限制,因为实现者可以使用任何其他参数类进行初始化。但主要是这违反了界面为消费者提供保障的原则,而不是对其实施者的限制。
把这些放在一起,你可以简单地去...
GaussianBlurParams
......你已经避免了所面临的所有问题。
即使您需要Params
约束IFilterParams
接口的另一个使用者(可能还有另一个您未在示例中添加的接口成员) ,您的public interface IFilter
{
void Apply(ref Mat buffer);
}
不需要此约束。因此,您仍然可以保留非通用T
并提供另一个提供附加功能的接口(可能会或可能不会继承自where T : IFilterParams
)。
答案 1 :(得分:2)
好的,感谢@zzxyz的原始评论以及添加的评论和答案,然后在删除后不久,让我更多地研究协方差(我通过添加通用IFilterParams创建以避免协方差),以及SO(Contravariance? Covariance? What's wrong with this generic architecture...?)中的答案/评论帮助我纠正了我的问题并更好地构建了代码。
现在我明白当我需要将香蕉加入碗中时,我是如何'在碗中加入香蕉(水果)(水果是协变,因为它不仅仅是一种“水果”)(香蕉)'。我理解但很难总结其中一个不幸删除的答案。
在研究中,我能够通过为filterParams创建一个具有自己的泛型类型的抽象类并完全删除IFilterParams接口来删除协方差,因此所有过滤器必须实现基本抽象类,现在不再导致协方差。
由于我现在明白了,但不能很好地解释(上面),修改后的代码(下面)可能有助于更好地解释。
首先,不需要对FilterTest类进行任何更改(除了从原始示例中删除转换,这是问题的关键点):
public class FilterTest
{
private FilterCollection filters = new FilterCollection();
public void ApplyFilters(ref Mat buffer)
{
var filter = new GaussianBlur(new GaussianBlurParams { KernelSize = new Size(6, 6) });
filters.Enqueue(filter);
filters.Apply(ref buffer);
}
}
接下来,调整了Queue,使其不是协变的(实现一个'type'IFilter),它暴露了所需的'Apply'方法。
public class FilterCollection : Queue<IFilter>
{
public void Apply(ref Mat buffer)
{
while (Count > 0)
Dequeue().Apply(ref buffer);
}
}
public interface IFilter
{
void Apply(ref Mat buffer);
}
最后删除了IFilterParams,因为它们不再与原因相关。现在示例过滤器实现如下:
public class GaussianBlur : IFilter
{
private GaussianBlurParams p;
public GaussianBlur(GaussianBlurParams filterParams)
: base(filterParams)
{
}
public override void Apply(ref Mat buffer)
{
Cv2.GaussianBlur(buffer, buffer, p.KernelSize, p.SigmaX, p.SigmaY, p.BorderType);
}
}
public class GaussianBlurParams
{
public Size KernelSize = new Size(5, 5);
public double SigmaX = default(double);
public double SigmaY = default(double);
public BorderTypes BorderType = BorderTypes.Default;
}
问题已解决,希望能帮助他人!