2017-07-07 19 views
5

Tôi có hai giao diện:Không thể cast đối tượng của loại chung để chung giao diện C#

public interface IDbModel {} 
public interface IDmModel {} 

Và các lớp học có nguồn gốc từ này:

public class DbModel : IDbModel {} 
public class DmModel : IDmModel {} 
public class Middle { } 

Ngoài ra tôi có hai giao diện với những hạn chế:

public interface IRule { } 
public interface IRule<in TInput, out TOutput> : IRule 
    where TInput : IDmModel 
    where TOutput : IDbModel 
{ 
    TOutput Apply(TInput elem); 
} 

Và một lớp trừu tượng bắt nguồn từ giao diện này:

public abstract class Rule<TDmModel, TMiddle, TDb> : IRule<TDmModel, TDb> 
    where TDmModel : IDmModel 
    where TDb : IDbModel 
{ 
    private readonly Func<TDmModel, TMiddle> _rule; 
    protected Rule(Func<TDmModel, TMiddle> rule) { _rule = rule; } 
    protected abstract TDb Apply(TMiddle transformedMessage); 
    public TDb Apply(TDmModel elem) { ... } 
} 

Sau này, tôi đã tạo ra hai lớp học có nguồn gốc từ lớp trừu tượng này:

public class RuleA : Rule<DmModel, Middle, DbModel> 
{ 
    public RuleA(Func<DmModel, Middle> rule) : base(rule) {} 
    protected override DbMode Apply(Middle transformedMessage) { ... } 
} 

public class RuleB : RuleA 
{ 
    public RuleB() : base((dm) => new Middle()) {} 
} 

RuleB: RuleA: Rule < DmModel, Trung, DbModel>: IRule < IDmModel, IDbModel>: IRule

Và khi tôi cố gắng truyền đối tượng của RuleB đến IRule<IDmModel, IDbModel> occours unhandled exception

Không thể truyền đối tượng thuộc loại 'ParsreCombinators.RuleB' để nhập 'ParsreCombinators.IRule`2 [ParsreCombinators.IDmModel, ParsreCombinators.IDbModel]'.

var ruleB = (IRule<IDmModel, IDbModel>)new RuleB(); // Exception 
IDbModel dbModel = ruleB.Apply(new DmModel()); 

gì sai với this

Để làm ví dụ ít gây nhầm lẫn tôi đơn giản hóa nó:

EDIT:

Sau câu trả lời tôi đã hiểu, vấn đề là gì và để làm cho ví dụ ít khó hiểu hơn, tôi đơn giản hóa nó:

public interface IDbModel {} 
public interface IDmModel {} 

public class DbModel : IDbModel {} 
public class DmModel : IDmModel {} 

public interface IRule<in TInput, out TOutput> 
    where TInput : IDmModel 
    where TOutput : IDbModel 
{ 
    TOutput Apply(TInput elem); 
} 

public class RuleA : IRule<DmModel, DbModel> 
{ 
    public DbModel Apply(DmModel elem) { ... } 
} 

var ruleA = (IRule<IDmModel, IDbModel>)new RuleA(); // Exception 

Trả lời

7

Đó là rất nhiều mức độ gián tiếp bạn đến đó ...

Dưới đây là vấn đề:

public abstract class Rule<TDmModel, TMiddle, TDb> : IRule<TDmModel, TDb> 
    where TDmModel : IDmModel 
    where TDb : IDbModel 

public class RuleA : Rule<DmModel, Middle, DbMode> 
public class RuleB : RuleA 
... 
var ruleB = (IRule<IDmModel, IDbModel>)new RuleB(); 

RuleB thực hiện IRule < DmModel, DbMode >

này không thể được đúc để IRule < IDmModel, IDbModel >. C# không hỗ trợ loại truyền này.Đối với lý do tương tự, bạn không thể làm List<object> b = (List<object>)new List<string>(); (Cung cấp cho "Không thể chuyển đổi loại 'System.Collections.Generic.List < chuỗi > để System.Collections.Generic.List < đối tượng >.")

Đây là một vấn đề với hiệp phương sai .

Dưới đây là một số thông tin từ Microsoft về đề tài này: https://docs.microsoft.com/en-us/dotnet/standard/generics/covariance-and-contravariance

+1

@kogoia Tôi đã đào sâu hơn một chút và nhận thấy rằng bạn đã khai báo tham số giao diện của mình dưới dạng 'in' và' out' tương ứng. Điều này có nghĩa rằng bạn thực sự có thể làm điều này: 'var ruleB = (IRule ) mới RuleB();' DmModel phải được cụ thể trong trường hợp này, nhưng IDbModel có thể được để lại như một giao diện trong diễn viên. Điều đó có giúp ích gì cho những gì bạn đang cố gắng làm không? –

+0

Không hoàn toàn, tôi muốn có được kết quả như trên '(IRule ) new RuleB()' – kogoia

+0

'public RuleB(): base ((dm) => new Middle()) {}' Tôi muốn rằng 'dm' là' DmModel' ở đây '(dm) => new Middle (dm.SomeProperty)', 'SomeProperti' là thuộc tính của lớp DmModel không có trong khai báo giao diện và không thể là – kogoia

3

Đây là một ví dụ khá khó hiểu, nhưng tôi tin rằng vấn đề là bạn đang chuyển sang kiểu chung và đưa ra giao diện, khi các lớp bạn tạo ra đang buộc bạn phải sử dụng DmModel và DbMode.

Có lẽ đây là những gì bạn có nghĩa là:

var ruleB = (IRule<DmModel, DbMode>)new RuleB(); 

Đó biên dịch tốt, và tôi với cách mà các lớp học của bạn được cấu trúc, là lựa chọn duy nhất khác hơn là tái cấu trúc.

+0

Có nó biên dịch và hoạt động, nhưng tôi không biết loại của các lớp này khi tôi đúc. Tôi có nghĩa là 'DmModel' và' DbModel' – kogoia

+1

Vấn đề của OP không được biên dịch, mà là một ngoại lệ thời gian chạy. Tuy nhiên, đề xuất này dường như giải quyết vấn đề. – CoderDennis

3

giao diện của bạn IRule<in TInput, out TOutput> là cả hai hiệp biến contravariant, có nghĩa là bạn không thể covariantly cast nó. Các contravariance ngăn chặn điều này.

Về cơ bản, nhiệm vụ của bạn var dbModel = (IRule<IDmModel, IDbModel>)new RuleB(); xác nhận rằng dbModel phải chấp nhận bất kỳ thông sốIDmModel nào. Thật không may, điều này là không đúng sự thật; các phiên bản phải được gán cho DmModel do hình thức cụ thể của RuleA, do đó các dẫn xuất khác của IDmModel sẽ không thành công.

+0

Đây là giải thích tốt về TẠI SAO loại hình truyền này không được phép. –

Các vấn đề liên quan