2015-12-16 60 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> 
{ 
}