从协变接口派生时,协变会丢失
Covariance gets lost when deriving from covariant interface
在我们的 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>)
?
您的接口继承是向后的。创建 interface IControlWithLabel
,然后让 IControlWithLabel<T>
继承自 IControlWithLabel
。那么 IControlWithLabel<T>
的所有实例也是 IControlWithLabel
.
您的实现需要像这样继承通用接口和非通用接口:
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");
}
唯一的问题是您将丢失 .Control
属性,除非您检查 is IControlWithLabel<TextBox>
。
除非我遗漏了某些要求,否则您不需要协方差?
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>
{
}
感谢所有其他答案,我终于找到了解决方案。 None 的答案使得使用一般的 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>)
是件好事。它在某些情况下很有用。
此解决方案已经过测试并且有效。
在我们的 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>)
?
您的接口继承是向后的。创建 interface IControlWithLabel
,然后让 IControlWithLabel<T>
继承自 IControlWithLabel
。那么 IControlWithLabel<T>
的所有实例也是 IControlWithLabel
.
您的实现需要像这样继承通用接口和非通用接口:
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");
}
唯一的问题是您将丢失 .Control
属性,除非您检查 is IControlWithLabel<TextBox>
。
除非我遗漏了某些要求,否则您不需要协方差?
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>
{
}
感谢所有其他答案,我终于找到了解决方案。 None 的答案使得使用一般的 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>)
是件好事。它在某些情况下很有用。
此解决方案已经过测试并且有效。