从协变接口导出时会丢失协变性

本文关键字:接口 | 更新日期: 2023-09-27 18:27:10

在C#.NET WinForm应用程序中,我们有一个名为ControlWithLabel的自定义控件。我想通过模板将其增强到ControlWithLabel<TControl>。问题是,我们有数百个像if (something is ControlWithLabel)这样的检查,并且测试对象可以是多种派生类型(TextBoxWithLabel、ComboBoxWithLabel等)。我如何将其转换为模板解决方案,而不需要重写每个检查并将其与各种可能性相乘,比如if (something is ControlWithLabel<TextBox>) || (something is ControlWithLabel<ComboBox>) || ... etc ...

我尝试使用协变接口,但它并没有像我预期的那样工作。当派生到通用非模板接口时,接口的协方差会丢失。

public class ControlWithLabel<TControl> : IControlWithLabel<TControl> where TControl : Control, new()
{
    public ControlWithLabel()
    {
        this.Control = new TControl();
        this.Label = new Label();
    }
    public Label Label
    {
        get;
        private set;
    }
    public TControl Control
    {
        get;
        private set;
    }
}
public class ControlWithLabel : ControlWithLabel<Control>, IControlWithLabel
{
}
public interface IControlWithLabel<out TControl> where TControl : Control
{
    Label Label
    {
        get;
    }
    TControl Control
    {
        get;
    }
}
public interface IControlWithLabel : IControlWithLabel<Control>
{
}
public class TextBoxWithLabel : ControlWithLabel<TextBox>
{
    public void SpecialMethodForTextBox()
    {
        // Special code ...
    }
}
public partial class FormMain : Form
{
    public FormMain()
    {
        InitializeComponent();
    }
    private void _buttonTest_Click(object sender, EventArgs e)
    {
        TextBoxWithLabel textBoxWithLabel = new TextBoxWithLabel();
        // this works, but then I need to rewrite and multiply every check
        if (textBoxWithLabel is ControlWithLabel<TextBox>)
            MessageBox.Show("textBoxWithLabel is ControlWithLabel<TextBox>");
        // this is not working, since classes cannot be covariant
        if (textBoxWithLabel is ControlWithLabel<Control>)
            MessageBox.Show("textBoxWithLabel is ControlWithLabel<Control>");
        // this is not working at all
        if (textBoxWithLabel is ControlWithLabel)
            MessageBox.Show("textBoxWithLabel is ControlWithLabel");
        // this works, but then I need to rewrite and multiply every check
        if (textBoxWithLabel is IControlWithLabel<TextBox>)
            MessageBox.Show("textBoxWithLabel is IControlWithLabel<TextBox>");
        // this works, but then I need to rewrite every check
        if (textBoxWithLabel is IControlWithLabel<Control>)
            MessageBox.Show("textBoxWithLabel is IControlWithLabel<Control>");
        // this is not working - covariance is lost!! Why?
        if (textBoxWithLabel is IControlWithLabel)
            MessageBox.Show("textBoxWithLabel is IControlWithLabel");
    }
}

我应该怎么做才能通常使用if (something is ControlWithLabel)if (something is IControlWithLabel)而不是if (something is IControlWithLabel<Control>)

从协变接口导出时会丢失协变性

除非我遗漏了一些要求,否则您不需要协方差?

public interface IControlWithLabel
{
    Label Label { get; }
}
public interface IControlWithLabel<TControl> : IControlWithLabel where TControl : Control, new()
{  
    TControl Control { get;}
}
//never instantiated, common base
public abstract class ControlWithLabel : IControlWithLabel
{
}
public class ControlWithLabel<TControl> : ControlWithLabel, IControlWithLabel<TControl>
{
}

您的实现需要继承泛型和非泛型接口,例如:

public class TextBoxWithLabel : ControlWithLabel<TextBox>
{
    public void SpecialMethodForTextBox()
    {
        // Special code ...
    }
}
public class ControlWithLabel<TControl> 
    IControlWithLabel, 
    IControlWithLabel<TControl> where TControl : Control, new()
{
    public ControlWithLabel()
    {
        Control = new TControl();
        Label = new Label();
    }
    public Label Label { get; private set; }
    public TControl Control { get; private set; }
}
public interface IControlWithLabel<out TControl> where TControl : Control
{
    Label Label { get; }
    TControl Control { get; }
}
public interface IControlWithLabel
{
    Label Label { get; }
}

然后你应该能够做到以下几点:

var textBoxWithLabel = new TextBoxWithLabel();
// This will work just fine
if (textBoxWithLabel is IControlWithLabel)
{
    MessageBox.Show("textBoxWithLabel is IControlWithLabel");
}

唯一的问题是,除非检查is IControlWithLabel<TextBox>,否则您将丢失.Control属性。

您的接口继承是向后的。创建interface IControlWithLabel,然后让IControlWithLabel<T>IControlWithLabel继承。那么CCD_ 13的所有实例也是CCD_。

感谢所有其他答案,我终于找到了解决方案。没有一个答案使得使用通用ControlWithLabel.ControlIControlWithLabel.Control成为可能。这是我的解决方案:

public interface IControlWithLabel
{
    Label Label
    {
        get;
    }
    Control Control
    {
        get;
    }
}
/// <summary>
/// Here we use covariance
/// </summary>
public interface IControlWithLabel<out TControl> : IControlWithLabel where TControl : Control, new()
{
    new TControl Control
    {
        get;
    }
}
/// <summary>
/// Common base, never instantiated
/// </summary>
public abstract class ControlWithLabel : IControlWithLabel
{
    protected Control _control;
    public ControlWithLabel()
    {
        this.Label = new Label();
    }
    public Label Label
    {
        get;
        private set;
    }
    /// <summary>
    /// This property cannot be marked as 'abstract', because we want to change the return type in descendants
    /// </summary>
    public virtual Control Control
    {
        get
        {
            return _control;
        }
    }
}
public class ControlWithLabel<TControl> : ControlWithLabel, IControlWithLabel<TControl> where TControl : Control, new()
{
    public ControlWithLabel() : base()
    {
        this.Control = new TControl();
    }
    /// <summary>
    /// We cannot use 'override', since we want to return TControl instead of Control
    /// </summary>
    public new TControl Control
    {
        get
        {
            return _control as TControl;
            // This will return null if _control is not TControl.
            // This can happen, when we make an explicit cast for example TextBoxWithLabel to ComboBoxWithLabel, which requires an explicit conversion operator implementation.
            // In such case there can be still used general ControlWithLabel.Control, which always will be "not null" - for example ((ControlWithLabel)someObject).Control
            // (the general ControlWithLabel.Control will always be "not null", because the base class ControlWithLabel is marked as abstract and current class ControlWithLabel<TControl> creates the control in the constructor).
        }
        private set
        {
            _control = value;
        }
    }
}
public class TextBoxWithLabel : ControlWithLabel<TextBox>
{
    public void SpecialMethodForTextBox()
    {
        // Special code ...
    }
}
public partial class FormMain : Form
{
    public FormMain()
    {
        InitializeComponent();
    }
    private void _buttonTest_Click(object sender, EventArgs e)
    {
        TextBoxWithLabel textBoxWithLabel = new TextBoxWithLabel();
        // This works
        if (textBoxWithLabel is ControlWithLabel<TextBox>)
        {
            // We can use the general ControlWithLabel.Control
            if (((ControlWithLabel)textBoxWithLabel).Control != null)
                MessageBox.Show("textBoxWithLabel is ControlWithLabel<TextBox>");
        }
        // This is not working, since classes cannot be covariant
        if (textBoxWithLabel is ControlWithLabel<Control>)
        {
            // We can use the general ControlWithLabel.Control
            if (((ControlWithLabel)textBoxWithLabel).Control != null)
                MessageBox.Show("textBoxWithLabel is ControlWithLabel<Control>");
        }
        // This works!
        if (textBoxWithLabel is ControlWithLabel)
        {
            // We can use the general ControlWithLabel.Control
            if (((ControlWithLabel)textBoxWithLabel).Control != null)
                MessageBox.Show("textBoxWithLabel is ControlWithLabel");
        }
        // This works
        if (textBoxWithLabel is IControlWithLabel<TextBox>)
        {
            // We can use the general INTERFACE property IControlWithLabel.Control
            if (((IControlWithLabel)textBoxWithLabel).Control != null)
                MessageBox.Show("textBoxWithLabel is IControlWithLabel<TextBox>");
        }
        // This works thanks to COVARIANCE
        if (textBoxWithLabel is IControlWithLabel<Control>)
        {
            // We can use the general INTERFACE property IControlWithLabel.Control
            if (((IControlWithLabel)textBoxWithLabel).Control != null)
                MessageBox.Show("textBoxWithLabel is IControlWithLabel<Control>");
        }
        // This works!
        if (textBoxWithLabel is IControlWithLabel)
        {
            // We can use the general INTERFACE property IControlWithLabel.Control
            if (((IControlWithLabel)textBoxWithLabel).Control != null)
                MessageBox.Show("textBoxWithLabel is IControlWithLabel");
        }
    }
}

在这种情况下并不需要协方差,但有可能检查if (textBoxWithLabel is IControlWithLabel<Control>)是件好事。在某些情况下,它可能很有用。

此解决方案经过测试并有效。