从协变接口导出时会丢失协变性
本文关键字:接口 | 更新日期: 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.Control
和IControlWithLabel.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>)
是件好事。在某些情况下,它可能很有用。
此解决方案经过测试并有效。