如何在C#/.Net中表示线性类型?

14

有没有一种合理的方法在.Net(Compact Framework/desktop 3.5 common subset)中表达线性类型的概念,使得(a)所需的语法不会变得过于冗长、复杂或者痛苦,并且(b)不变式可以在运行时强制执行或者通过代码分析在编译时验证(这样一个急于维护的程序员就不能漫不经心地忽略不变式)?这里的想法是避免在子系统边界处进行命令对象的防御性复制。


1
仅使用不可变对象是否足够?看起来简单得多?我不知道是否有任何东西可以在这里进行必要的代码分析。 - Marc Gravell
@Marc,“冰棒”不可变性(组合一个可变对象子图,然后将其“冻结”以防止进一步的变异)可能有效。我可以很容易地编写一个FxCop规则来验证它。 - Jeffrey Hantin
1
自由zable类怎么样?或者在您的用例中不适用? - Mike Miller
1
@Jeffrey - 我经常使用Popsicle不可变性;比“构建器”类容易得多。 - Marc Gravell
@Mike 感谢你的提示!非常有趣的课程。它是 WPF 的一部分,但不受 Compact Framework 支持,因此我可能需要重新实现它,可能没有依赖属性和更改通知负担,并且具有比每个线程更细的上下文区分。 - Jeffrey Hantin
@Jeffrey 不用担心,很抱歉我没有注意到需要使用紧凑框架,我应该仔细阅读问题。 - Mike Miller
3个回答

1

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

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

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

在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);
    }
}

好的,所以你有一个不可变类型。那怎么帮助你创建线性类型呢? - svick
内存管理是定义线性对象类型的关键。对于不可变对象的内存管理更加高效,因为不可变对象的内存具有固定的尺寸,并且要么被使用,要么可用。因此,这些类型非常适合用于线性数据结构。内存管理器不必拆分内存、扩展内存或执行当前大多数内存管理器执行的任何昂贵操作。这些假设使用线性数学,极大地加速了分配和释放。 - Ramprasad
1
好的,在识别线性类型的系统中可能是真的。但是.Net不支持,因此速度与使用可变类型时相同。而且OP似乎更关心的是您可以确保只有一个对象引用,而您的不可变类型并没有以任何方式强制执行这一点。 - svick
由于 .Net 不会识别线性类型,因此我们将其转换为不可变类型,这样在执行操作时我们可以使用相同的类型,在表示时可以以图形格式或任何其他格式表示线性类型。例如,圆可以被解释为一系列点,因此对其进行操作将变得容易,因为现在圆表示为一个数组。 - Ramprasad

1

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

当你通过将引用类型赋值给另一个变量来复制它时,只会复制引用。

当你复制值类型时,整个类型的内容都会被逐字节复制。

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

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


0
提供的链接真正定义了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包装器本身的别名引用,也没有提供任何防止保留对分配到变量中的对象的引用的方法。 - Jeffrey Hantin
@JeffreyHantin:没错,正如我所说的,这只是运行时的努力。我相信你需要编译器支持才能获得其中任何一项功能。具体来说,您可以获得一个FxCop规则,注意到LinearVariable的别名(实际上它应该仅存在于变量声明中,而不是作为例程参数),同样地保留.Value的内容,尽管您无法控制编译器将.Value存储在临时匿名本地变量中。 - Mark Hurd
1
请注意,问题的标题包括“C#”。 - Reinderien
@Reinderien:我理解为C#或.NET,并且它没有C#标签——不是说这会阻止我发布这个帖子:VB.NET可以被任何关心的人转换为C#。(我这么说是因为通常是反过来说的。) - Mark Hurd

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接