簡體   English   中英

C#:轉換為具有基類型的泛型接口

[英]C#: cast to generic interface with base type

這是代碼:

public interface IValidator<T>
{
   bool IsValid(T obj);
}

public class OrderValidator: IValidator<Order>
{
  // ...
}

public class BaseEntity
{
}

public class Order: BaseEntity
{
}

問題是我不能這樣做:

var validator = new OrderValidator();
// this line throws because type can't be converted
var baseValidator = (IValidator<BaseEntity>)validator;
// all this is because I need a list with IValidator<Order>, IValidator<BaseOrder>, etc.
IList<IValidator<BaseEntity>> allValidators = ... 

如何獲取和存儲基礎 T 的 IValidator<T> 所有實現的列表 - 例如 BaseEntity? 目前我做的非通用 IValidator 接受“對象 obj”,但它不好,也不是類型安全的。

有趣的是,C#允許編譯:

var test = (IValidator<BaseEntity>)new OrderValidator();

但在運行時失敗

   Unable to cast object of type 'OrderValidator' to type 'IValidator`1[Orders.Core.BaseEntity]'

這與 Windsor 給我的異常相同(我嘗試了 Windsor 和手動類型查找,這個問題實際上與此無關,僅與接口轉換有關)。

感謝 Heinzi,我現在明白為什么我不能轉換了 - 因為 IValidator for Order 期望 Order 作為泛型類型。 但是如何返回不同類型的 IValidator 列表? 原因是 BaseEntity 獲取其真實類型並收集所有驗證器 - 從 GetType() 到對象的所有類型。 我真的很想擁有一個通用的 GetValidators() 然后對其進行操作。

如果我解釋為什么禁止這種強制轉換可能會幫助你:假設你有以下功能

void myFunc(IValidator<BaseEntity> myValidator) {
    myValidator.IsValid(new BaseEntity());
}

此代碼將正確編譯。 然而,如果您將OrderValidator傳遞給該函數,則會出現運行時異常,因為OrderValidator.IsValid需要一個 Order,而不是 BaseEntity。 如果你的演員被允許,類型安全將不再被維護。

編輯:C# 4 允許通用 co- 和 contravariance ,但這對您的情況沒有幫助,因為您使用 T 作為輸入參數。 因此,只能以類型安全的方式轉換為IValidator<SomeSubtypeOfOrder>

因此,需要明確的是,您不能將OrderValidatorIValidator<BaseEntity>因為您的 OrderValidator 只能驗證訂單,而不能驗證所有類型的 BaseEntities。 然而,這是IValidator<BaseEntity>所期望的。

IValidator<Order>不起作用,因為IValidator<Order>IValidator<BaseEntity>是完全不相關的類型。 IValidator<Order>不是IValidator<BaseEntity>的子類型,因此它們不能被IValidator<BaseEntity>

C# 確實支持多接口繼承,因此處理此問題的最簡單方法是讓您的訂單驗證器從兩個驗證器類型的接口繼承,這樣您就可以根據需要將其轉換為任一接口。 顯然,這意味着您必須實現兩個接口並指定當​​提供的BaseEntity與驗證器的類型不匹配時如何處理基類。

像這樣的東西:

public class OrderValidator : IValidator<Order>, IValidator<BaseEntity>
{
    public bool IsValid(Order obj)
    {
        // do validation
        // ...
        return true;
    }

    public bool IsValid(BaseEntity obj)
    {
        Order orderToValidate = obj as Order;
        if (orderToValidate != null)
        {
            return IsValid(orderToValidate);
        }
        else
        {
            // Eiter do this:
            throw new InvalidOperationException("This is an order validator so you can't validate objects that aren't orders");
            // Or this:
            return false;
            // Depending on what it is you are trying to achive.
        }
    }
}

這與Heinzi所說的無法進行轉換有關,因為IValidator<BaseEntity>需要能夠驗證BaseEntities ,而您當前的OrderValidator無法做到這一點。 通過添加這個多接口,您可以顯式定義驗證BaseEntities的行為(通過顯式忽略它或導致異常),以便轉換成為可能。

雖然這不會直接回答您,但我建議您查看 StructureMap 的源代碼,它們對開放的泛型類型做了很多工作。 實際上甚至可能想使用 StructureMap 來處理驗證器的緩存,這正是我所做的。

ForRequestedType(typeof (ValidationBase<>)).CacheBy(InstanceScope.Singleton);
Scan(assemblies =>
    {
        assemblies.TheCallingAssembly();
        assemblies.AddAllTypesOf(typeof(IValidation<>));
    });

然后我有一個工廠類來做實際的驗證

public static class ValidationFactory
{
    public static Result Validate<T>(T obj)
    {
        try
        {
            var validator = ObjectFactory.GetInstance<IValidator<T>>();
            return validator.Validate(obj);
        }
        catch (Exception ex)
        {
            ...
        }
    }
}

編輯:我寫了一篇關於使用 IoC 進行泛型驗證的大型博客文章,如果你說你已經在使用 Spring,那么你看看它,我敢打賭你可以調整我的工作來解決你的問題: 創建通用驗證框架

暫無
暫無

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

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