【问题标题】:How to represent linear types in C#/.Net?如何在 C#/.Net 中表示线性类型?
【发布时间】:2011-09-20 03:15:43
【问题描述】:

是否有一种合理的方式来表达 .Net(Compact Framework/desktop 3.5 通用子集)中的 linear type 的概念,使得 (a) 所需的语法不会变得过于冗长、复杂,或者以其他方式痛苦并且(b)不变量可以在运行时强制执行,也可以在编译时通过代码分析进行验证(所以一个急忙的维护程序员不能只是愉快地忽略不变量)?这里的想法是避免在子系统边界处对command objects 进行防御性复制。

【问题讨论】:

  • 只使用不可变对象就足够了吗?看起来简单多了?我不知道有什么可以在这里进行必要的代码分析
  • @Marc "Popsicle" 不变性(组成一个可变对象子图,然后“冻结”它以防止进一步的突变)可能有效。我可能会汇总一个 FxCop 规则来轻松验证这一点。
  • Freezable 类呢?还是不适用于您的用例?
  • @Jeffrey - 我经常使用冰棒不变性; so 比“builder”类容易得多。
  • @Mike 感谢您的提示!很有趣的课。它是 WPF 的一部分,但在 Compact Framework 中不受支持,因此我必须重新实现它,可能没有依赖属性和更改通知包,并且具有比每个线程更好的上下文区分。

标签: .net linear-types


【解决方案1】:

.Net 中有两种类型:引用类型和值类型。

当您通过将引用类型分配给另一个变量来复制引用类型时,只会复制引用。

当你复制一个值类型时,该类型的全部内容被逐字节复制。

在这两种情况下,都无法阻止、修改或获取有关它的通知(与 C++ 的复制构造函数相反)。这意味着您无法在 .Net 中实现线性类型。

您可以改用不可变(或可冻结)类型,正如其他人建议的那样。

【讨论】:

    【解决方案2】:

    线性类型,基于线性逻辑理论,与唯一性类型密切相关,是分配给具有属性的类型的类型,即它们始终只有一个引用。这些对于描述大的不可变值(如文件、字符串等)很有价值。

    不可变类型是其内部状态在实例化后无法更改的类型。

    “深度不可变”类型是其依赖图包含的引用类型也是“深度不可变”的类型。如果依赖引用类型本身不是“深度不可变”,则该类型被称为“浅不可变”。

    在 C# 中,我们使用引用类型和值类型。引用类型的实例可以在不同的并发执行的代码之间共享,而值类型是堆栈绑定的(除非装箱),在共享时复制,因此是自治的,尽管不是不可变的(并且可能包含对其他引用类型的依赖关系,然后“复制共享” )。

    虽然共享引用类型的能力无疑是面向对象框架的一个强大功能,但在企业开发领域,它也应该被视为其主要弱点之一,并应极其谨慎地使用。任何不能以原子方式执行的事情都会暴露脆弱性,并有机会交织错误以间歇性地造成严重破坏。

    在 C# 中,我们能做的最好的事情就是描述我们的意图。可以通过将类型的整个内部状态标记为私有和只读来部分实现不变性。无法强制执行深度不变性(也不能执行浅层不变性),因此由开发人员决定是否坚持意图。状态更改是通过静态方法返回包含请求状态的类型的新实例。

    public sealed class PersonImmutable {
    
        private readonly int _age;
        private readonly string _name;
    
        public PersonImmutable(int age, string name) { 
            this._age = age;
            this._name = name;
        }
    
        public int Age {
            get { return this._age; }
        }
    
        public string Name {
            get { return this._name; }
        }
    
        public static PersonImmutable NotifyBirthday(PersonImmutable source) {
            return new PersonImmutable(1 + source.Age, source.Name);
        }
    }
    

    【讨论】:

    • 好的,所以你有一个不可变类型。这如何帮助您创建线性类型?
    • 内存管理是定义线性对象的关键。不可变对象的内存管理效率更高,因为不可变对象内存具有固定维度,并且要么已使用,要么可用。因此,如果这些类型可以很好地用于线性数据结构。内存管理器不必拆分内存、扩展内存或执行大多数内存管理器当前执行的任何昂贵操作。这些假设使用线性数学并大大加快了分配和释放。
    • 好的,这在识别线性类型的系统中可能是正确的。但是 .Net 没有,因此速度与使用可变类型时相同。并且 OP 似乎对这样一个事实更感兴趣,即您可以确定只有一个对象引用,而您的不可变类型不会以任何方式强制执行。
    • 由于 .Net 无法识别线性类型,因此我们将其设置为不可变的,以便在进行操作时使用相同的类型,并且在表示时我们可以以图形格式或任何形式表示线性类型其他格式。例如 circle 可以在 .Net 中解释为一系列点,因此对它的操作将变得容易,因为 circle 现在表示一个数组。
    【解决方案3】:

    提供的链接确实定义了LinearVariable,可以这样定义:

    Option Explicit On
    Option Strict On
    Option Infer On 
    
    <System.Diagnostics.DebuggerDisplay("{_state}: {_value}")> _
    Class LinearVariable(Of T)
      Private Enum State
       Unassigned
       Assigned
       Used
     End Enum
     Private _state As State = State.Unassigned
     Private _value As T
     Public Sub New()
      'Allow creation and later assignment
     End Sub
     Public Sub New(ByVal Value As T)
      _value = Value
      _state = State.Assigned
     End Sub
     Public Shared Widening Operator CType(Value As T) As LinearVariable(Of T)
      Return New LinearVariable(Of T)(Value)
     End Operator
     Public Shared Widening Operator CType(Value As LinearVariable(Of T)) As T
      Return Value.Value
     End Operator
     Public Property Value As T
      Get
       If _state = State.Assigned Then
        _state = State.Used
    #If DEBUG Then
        Return _value
    #Else ' Release - free the reference immedately after use
        value = _value
        _value = Nothing
    #End If
       End If
       If _state = State.Unassigned Then _
        Throw New NullReferenceException("LinearVariable is unassigned")
       If _state = State.Used Then _
        Throw New AccessViolationException("LinearVariable has already been accessed")
       Throw New InvalidOperationException
      End Get
      Set(ByVal Value As T)
       ' May want to check _state, although the "definition" at http://c2.com/cgi/wiki?LinearTypes seems to allow multiple writes
       _value = Value
       _state = State.Assigned
      End Set
     End Property
    End Class
    

    (已编译但未经测试。)

    这显然只在运行时有效。我想不出任何方法来尝试在编译时强制使用.Value

    请注意,您可以创建 LinearVariable IDisposable,然后在其值已设置但未使用时在运行时捕获。

    【讨论】:

    • 这不提供捕获对 LinearVariable 包装器本身的别名引用,也不提供任何方法来防止保留对分配给变量的对象的引用。
    • @JeffreyHantin:没错,正如我所说,这只是运行时的工作。我很确定您需要编译器支持才能获得这些东西。具体来说,您可能能够获得一个 FxCop 规则,该规则注意到 LinearVariable 的别名(实际上它应该仅作为变量声明而不作为例程的参数存在)并且类似地用于保留 .Value 的内容,尽管您会无法控制将.Value 存储在临时匿名局部变量中的编译器。
    • 注意问题的标题中包含“C#”。
    • @Reinderien:我把它读作 C# .NET,它没有 C# 作为标签——这并不一定会阻止我发布这个:任何关心的人都可以将 VB.NET 转换为 C#。 (我这样说是因为它通常是反过来说的。)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-02-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多