簡體   English   中英

C#泛型:無法從“具體類”轉換為“接口”錯誤

[英]C# Generics: cannot convert from 'concrete class' to 'interface' error

嘗試生成要應用於圖像的過濾器的通用隊列(在示例過濾器中,它使用OpenCVSharp.GaussianBlur ,但使其成為通用的,因此我可以插入我創建的任何自定義過濾器)。

我在C#泛型方面有些掙扎,而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);

    }
}

我正在為FilterCollection擴展Queue <>類:

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

是使用強制轉換解決此問題的唯一方法,還是所編寫的通用類/接口的結構有問題?

糾纏於您的代碼的多個方面,這些方面使設計不盡人意。 乍一看,這看起來像是協方差問題,但仔細觀察卻並非如此。 這里的兩個主要方面是通用約束接口 為了理解我的意思,讓我們看一下這兩種語言元素的一些好處。

通用約束

盡管泛型使您能夠以類型安全的方式對多種類型使用模式的實現,但是您可以做很多事情來直接從泛型類中操縱類型T的對象。 您不能創建實例,不能依賴實例是引用或值類型(嘗試使用null進行比較以了解含義),並且不能訪問System.Object定義的成員以外的任何其他成員。 這就是為什么您可以使用泛型約束來允許泛型類中的代碼可以對類型T對象執行其他操作,例如創建實例(使用new()約束)或訪問其他成員(通過將T約束為特定類型和/或一個或多個接口)。

介面

接口提供了合同保證,即實現者將具有一組定義的成員。 此保證針對的是接口的使用者 ,而不是接口的實現者 這意味着您不會使用接口來強制其實現者提供一些對接口的使用者沒有任何價值的成員。

這對您而言意味着什么

解決問題的關鍵是代碼的這一部分:

public interface IFilter<T> where T : IFilterParams
{
    void Apply(ref Mat buffer);
}

public interface IFilterParams { }

特別是:

  1. 您可以where T : IFilterParams定義通用約束where T : IFilterParams ,但IFilterParams提供成員。 此約束不會為您的設計增加任何價值。 您將實現者限制為某個T ,但是您從中不能獲得任何收益,因為您無法使用T實例做任何事情,而沒有約束就無法做到。

  2. 再進一步,您根本不需要通用的接口。 您甚至不需要在接口提供的唯一成員中使用T 就接口的保證而言,沒有它就可以做得很好。

  3. 看一下IFilter<T>GaussianBlur實現,很明顯,您僅在構造函數中使用了GaussianBlurParams ,它不是接口的一部分。 因此,您僅使用接口的約束來限制實現者使用實現IFilterParamsParams類。 這甚至不是真正的限制,因為實現者可以使用任何其他參數類進行初始化。 但是,這主要違反了接口向其使用者提供保證而不是對其實現者的限制的原則。

放在一起,您可以簡單地...

public interface IFilter
{
    void Apply(ref Mat buffer);
}

...您已經避免了所面臨的所有問題。

即使您需要帶有約束的Twhere T : IFilterParams用於該接口的另一個使用者(也許您的示例中未添加另一個接口成員),您的FilterCollection也不需要此約束。 因此,您仍然可以保留一個非通用的IFilter並提供另一個提供附加功能的接口(該接口可以或可以不從IFilter繼承)。

好的,感謝@zzxyz的原始注釋以及添加的注釋和答案,然后在刪除之后不久,使我對協方差進行了更多研究(我通過添加通用IFilterParams來避免協方差而創建),以及答案/評論在SO中( Contravariance?Covariance?這種通用體系結構有什么問題?? )幫助我糾正了問題並更好地構造了代碼。

現在我了解了當我需要將香蕉添加到碗(香蕉)中時,如何嘗試將香蕉添加到碗(水果)中(水果是協變的,因為它不僅僅是一種“水果”類型)。 。 我了解但很遺憾地總結了不幸刪除的答案之一。

在研究中,我能夠通過為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);

    }
}

接下來,調整隊列,以使其不發生協變(實現一個“類型” IFilter),從而暴露所需的“應用”方法。

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

問題已解決,希望可以幫助其他人!

暫無
暫無

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

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