【问题标题】:Would "Constrained Types" be useful in VB/C#?“约束类型”在 VB/C# 中有用吗?
【发布时间】:2009-02-16 11:40:40
【问题描述】:

简介

这个问题是由 Marc Gravell 的建议提出的,我建议我在这个网站上发布新的语言功能建议以收集对它们的一般意见。

我们的想法是收集它们是否有帮助,或者是否已经有另一种方法可以实现我所追求的目标。

建议(约束类型)

VB.Net 中的普通变量声明是这样写的:

Dim SomeVariable as SomeType

我建议允许以下形式

Dim SomeVariable1 as {SomeType, ISomeInterface}
Dim SomeVariable2 as {SomeType, ISomeInterface, ISomeOtherInterface}

这个语法借鉴了 Vb.Net 的约束泛型风格

为什么允许这样做?...有什么好处?

嗯,我最初想到的具体案例是定义一个特定的控件子集。 我希望为一系列控件工厂创建一个接口,这些工厂将根据一些业务规则提供控件。

这些控件的使用者将通过接口要求创建的所有控件还应实现一些接口系列(在我的例子中只有一个),这些接口为所有这些控件提供了普通控件中通常不具备的额外功能。

值得注意的是,以下目前不起作用。

Public Interface ISpecialControl
End Interface

Public Interface IControlProvider
    Function CreateControl(Of T As {Control, ISpecialControl})() As T
End Interface

Public Class SpecialTextBoxProvider
    Implements IControlProvider
    Public Function CreateControl(Of T As {Control, ISpecialControl})() As T Implements IControlProvider.CreateControl
        Return New SpecialTextBox
    End Function
End Class

Public Class SpecialTextBox
    Inherits TextBox
    Implements ISpecialControl
    Public Sub New()

    End Sub
End Class

我认为这转换为 C# 为:

public interface ISpecialControl
{
}

public interface IControlProvider
{
    T CreateControl<T>()
        where T : Control, ISpecialControl;
}

public class SpecialTextBoxProvider : IControlProvider
{
    public T CreateControl<T>()
        where T : Control, ISpecialControl
    {
        return new SpecialTextBox();
    }
}

public class SpecialTextBox : TextBox, ISpecialControl
{
}

由于无法将 SpecialTextbox 强制转换为 T,返回“New SpecialTextbox”的尝试失败。

"Value of type 'MyApp.SpecialTextBox' cannot be converted to 'T'"

我意识到我的工厂可以被允许返回简单的控件,并且我可以在运行时检查它们是否实现了 ISpecialControl,但这会产生运行时问题,我宁愿在编译时检查,因为即使目前没有这种可能性也是合乎逻辑的一个实用的

更新:想法是这些工厂可以位于外部(甚至可能是第三方)程序集中,并且可以依赖于他们想要的任何控件库,创建并返回这些控件的派生类,这些派生类也实现了 ISpecialControl。

这些程序集可以通过自配置反射(第一次通过反射,然后是配置,然后在进一步运行中使用)定位,并且调用程序集在不知道这些控件将采取何种依赖关系的情况下使用这些程序集。

它确实要求这些工厂是可构造的,而无需传递有关它们期望调用的控件的信息,因为这会破坏这一点。

那么你觉得……这会有用吗?……有没有更好的方法来实现这一点?是否已经有办法实现这一目标?

【问题讨论】:

  • 您能提供一个 C# 示例吗?对于那些不了解 VB 的人来说,它会更容易阅读,并且还有语法着色的额外好处。
  • 我修复了 C# 部分的格式。不应该使用制表符,所以我用空格替换了它们。我还修改了SpecialTextBox.New() 以返回 void。 (我想这就是你的意图。如果不是,那么我道歉。)
  • 感谢 Hosam 的帮助。看起来好多了:)
  • 我在 C# 中删除了不必要的默认构造函数,无论如何它的语法不正确。使用 SpecialTextBox() 代替 New()。 ;)

标签: c# vb.net types constraints


【解决方案1】:

我认为,虽然这种事情表面上有用,但最好由两者之一来处理:

  • Duck Typing 通过动态(在 VS2010 中提供)实现了更大的灵活性,尽管消除了类型安全性。
  • 通用组合

您的原始示例两者都不需要,并且可以轻松地像这样工作:

public interface IControlProvider<T>
    where T : Control, ISpecialControl
{
    T CreateControl();
}

public class SpecialTextBoxProvider : IControlProvider<SpecialTextBox>
{
    public SpecialTextBox CreateControl()
    {
        return new SpecialTextBox();
    }
}

事实上,鉴于大多数控件都有一个默认的公共构造函数,你可以让它变得更简单:

public class ControlProvider : IControlProvider<T>
    where T : Control, ISpecialControl, new()
{
    public T CreateControl()
    {
        return new T();
    }
}

var control = ControlProvider<SpecialTextBox>.CreateControl();

鉴于对调用代码的额外限制,不直接对 SpecialTextBox 进行引用依赖,这是行不通的。

【讨论】:

  • 您的最后一行将 SpecialTextBox 作为泛型类型传入。如果您必须以如此直接的方式告诉它必须创建什么,这有点违背工厂的意义。要求调用代码直接依赖于 SpecialTextBox
  • 我已经更新了这个问题,以澄清当通过额外的程序集添加新工厂时,调用代码不应该有额外的依赖。
  • 是的,有了这个限制,这会更加复杂,但我建议这越来越多地进入了由于它导致的额外复杂性而带来的好处太少的领域。
  • 您认为约束类型会导致什么复杂性。说真的……如果它突然出现在下一个版本中……你能理解吗?会给你带来什么问题吗?我可以理解并非所有人都会使用它,但这不是重点。这是一个非常简单的语法添加。
  • 这不是语法,而是它的运作方式。它会改变 starter 的方法解析规则(更复杂)。这会影响动态(因为它被设计为具有与编译时相同的运行时行为)。这只是我的想法引用MS“一切都以-100开始”
【解决方案2】:

它比“受约束的类型”更多的是“受约束的变量”——但绝对有趣。与泛型约束一样,如果存在冲突的成员,可能会有一些解决问题的小问题,但可能与泛型约束一样适用相同的解决方法。

当然,务实的观点是你已经可以投射等,但是你需要保持这两个引用彼此保持最新......

嗯……很有趣。

【讨论】:

  • 我还没有尝试过,但我的理解是,如果我在返回时投射(使用“Trycast”或“As”),这可能(在这种情况下)会在运行时失败。
【解决方案3】:

使用界面组合怎么样?

你有

interface IA;
class B;

如果存在,则使用 B 类的接口并进行复合

interface IB;
interface IAB : IA, IB;

修改类

class C : B, IA

实现复合接口(应该不会出现问题)

class C : B, IAB

您可以使用接口 IAB 作为您的约束类型。

【讨论】:

  • 不起作用,因为我需要的两件事不是两个接口。一个是实际的类(在这种情况下为控制)。我可以完全重新实现 IControl 接口,但我无法将这种类型的项目添加到表单的 Controls 集合中。
  • 这迫使我在运行时进行强制转换,这会导致潜在的运行时错误,与编译时错误相比,这些错误更难捕获。 IControlProvider 的实施者可能是第 3 方,因此我想强制他们签订 {Control + ISpecialControl} 的合同
  • 为什么接口的实际实现很重要?为什么需要从界面投射?接口的目的是指定一个契约和任何实现所述接口的东西。如果不是这种情况,那么我怀疑您需要解决的代码还有其他问题。
  • 这个例子是关于生产控制的。除非将控件添加到表单中,否则控件是无用的。这只能通过将它们添加到只接受控件的控件集合中来完成。目前我只能保证 Control 或 ISpecialControl 的返回类型。我想确保两者
  • 这基本上是WinForms API的设计缺陷。不允许 IControl 对他们来说是一种简单的懒惰(当您控制基类并且可以放入内部方法供您自己使用时,制作小部件库要容易得多)。我不认为有缺陷的 API 设计值得这种改变
【解决方案4】:

我不明白你在提议什么,我认为这是我的问题。

但是,在我看来,您想返回一个实现特定接口的控件。

你为什么不能直接说方法返回那个特定的接口呢?听起来你在说“我想返回从 Control 继承的类,并且还实现接口 ISomeInterface”。

也许解决方案是将您需要的 Control 部分提取到您自己的接口中,并指定您要返回实现这两者的对象?

像这样:

interface IControl { ... }
interface ISomeInterface { ... }

interface IControlInterface : IControl, ISomeInterface { ... }

最后一个接口将指定您需要一个实现两个基接口的对象,这听起来像您想要的,只是您希望其中一个要求是基类,而不是接口。

我认为你可以通过使用接口来解决这个问题。

【讨论】:

  • 如果我只是将对象作为接口返回,如果不在运行时进行强制转换,我将无法将返回的对象添加到控件集合(在表单上)。如果我在运行时进行投射,我冒着工厂的创建者没有从控制中下降它的输出的风险。
  • 这可以被捕获,但只能在运行时捕获。我希望能够提供一个保证编译时适用性的合同。
【解决方案5】:

这与extension methods 有很大不同吗?为什么不只是扩展控件?

【讨论】:

    【解决方案6】:

    编辑:哦,等等,我没看错,这是关于将其限制为 2 种类型,而不是选择 2 种类型。我认为你可以解决它类似于下面提到的类...

    我曾经尝试过一个类来模拟这种行为:

    public class Choice<T1, T2> 
    {
        private readonly T1 value1;
        private readonly T2 value2;
    
        public Choice(T1 value)
        {
            value1 = value;
        }
    
        public Choice(T2 value) 
        {
            value2 = value;
        }
    
        public static implicit operator T1(Choice<T1, T2> choice) 
        {
            return choice.value1;   
        }
    
        public static implicit operator T2(Choice<T1, T2> choice) 
        {
            return choice.value2;   
        }
    
        public static implicit operator Choice<T1, T2>(T1 choice) 
        {
            return new Choice<T1, T2>(choice);
        }
    
        public static implicit operator Choice<T1, T2>(T2 choice) 
        {
            return new Choice<T1, T2>(choice);
        }
    
        public T1 Value1
        {
            get { return value1; }
        }
    
        public T2 Value2
        {
            get { return value2; }
        }
    }
    

    它允许您创建这样的结构:

    [TestMethod]
    public void TestChoice() 
    {
        TestChoice("Hello");
        TestChoice(new []{"Hello","World"});
    }
    
    private static void TestChoice(Choice<string[], string> choice)
    {
        string value = choice;
        string[] values = choice;
    
        if (value != null)
            Console.WriteLine("Single value passed in: " + value);
    
        if (values != null) {
            Console.WriteLine("Multiple values passed in ");
            foreach (string s in values)
                Console.WriteLine(s + ",");
        }
    }
    

    隐式运算符的缺点是它不会对接口进行隐式转换(它确实可以从接口类型转换),因此在尝试时传递 IEnumerable&lt;T&gt; 而不是 string[] 将不起作用将包含的值从 Choice&lt;IEnumerable&lt;T&gt;, T&gt; 转换回 IEnumerable&lt;T&gt;

    通常我宁愿只使用重载/接口顺便说一句,所以不要误会我的意思:)

    在您使用属性(并且因此不能使用重载)的情况下,它会更有用,例如仅允许 list&lt;string&gt;list&lt;ListItem&gt; 的 DataSource 属性。

    【讨论】:

      【解决方案7】:

      这似乎是一个有用的语言功能。除了遵守多个约束之外,可能有类型未知的方法参数,但是没有办法将这样的参数存储在字段中,以便它可以传递给这样的函数,甚至类型转换为通过了。

      不过,还有另一种方法可以实现您所追求的基本结果,尽管它有点笨拙。请参阅我的问题Storing an object that implements multiple interfaces and derives from a certain base (.net) 在哪里提供我能够制定的最佳方法。

      【讨论】:

        猜你喜欢
        • 2013-02-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-08-29
        • 1970-01-01
        相关资源
        最近更新 更多