2015-12-16 63 views
0

在我们的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>)

回答

0

感谢所有其他答案,我终于找到了解决方案。没有任何答案可以使用通用的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>)的可能性。在某些情况下它可能很有用。

此解决方案已经过测试并且可以正常工作。

0

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

+0

耶稣!你是对的!谢谢。我将在单独的答案中写入正确的代码。 –

+0

嗯......但...我怎么才能使用接口的属性,当我切换继承的顺序,并在代码中,我所知道的是事实,该对象是一些ControlWithLabel并具有控件和标签? –

0

你的实现需要同时继承了通用和非通用接口,例如:

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>

1

除非我错过了一些要求,你不需要协变?

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> 
{ 
}