创建用户定义类型 - 编码

适用于:SQL Server

编写用户定义类型 (UDT) 的定义时,必须根据是要将 UDT 作为类还是作为结构实现以及您所选择的格式和序列化选项来实现各种功能。

本节中的示例演示如何将点 UDT 实现结构(或 Visual Basic 中的结构)。 UDT 由作为属性过程实现的 X 和 Y 坐标组成。

定义 UDT 时需要下列命名空间:

Imports System  
Imports System.Data.SqlTypes  
Imports Microsoft.SqlServer.Server  
using System;  
using System.Data.SqlTypes;  
using Microsoft.SqlServer.Server;  

Microsoft.SqlServer.Server 命名空间包含 UDT 的各种属性所需的对象,System.Data.SqlTypes 命名空间包含表示程序集可用的 SQL Server 本机数据类型的类。 当然,还可能存在程序集正常运行所需的其他命名空间。 Point UDT 还使用 System.Text 命名空间处理字符串。

注意

不支持使用 /clr:pure 编译的 Visual C++ 数据库对象(如 UDT)。

指定属性

属性确定如何使用序列化来构造 UDT 的存储表示形式以及如何按值将 UDT 传输到客户端。

需要 Microsoft.SqlServer.Server.SqlUserDefinedTypeAttribute序列化属性是可选的。 还可以指定 Microsoft.SqlServer.Server.SqlFacetAttribute 来提供有关 UDT 的返回类型的信息。 有关详细信息,请参阅 CLR 例程的自定义属性

Point UDT 属性

Microsoft.SqlServer.Server.SqlUserDefinedTypeAttribute 将点 UDT 的存储格式设置为本机IsByteOrdered 设置为 true,这可以保证 SQL Server 中的比较结果与托管代码中发生的相同比较相同。 UDT 实现 System.Data.SqlTypes.INullable 接口,以使 UDT 为 null 感知。

以下代码片段显示 Point UDT 的属性

<Serializable(), SqlUserDefinedTypeAttribute(Format.Native, _  
  IsByteOrdered:=True)> _  
  Public Structure Point  
    Implements INullable  
[Serializable]  
[Microsoft.SqlServer.Server.SqlUserDefinedType(Format.Native,  
  IsByteOrdered=true)]  
public struct Point : INullable  
{  

实现为 Null 性

除了为您的程序集正确指定属性外,UDT 还必须支持为 Null 性。 加载到 SQL Server 中的 UDT 具有 null 感知性,但为了让 UDT 识别 null 值,UDT 必须实现 System.Data.SqlTypes.INullable 接口。

必须创建一个名为 IsNull 的属性,该属性需要确定值是否为 CLR 代码中的 null。 当 SQL Server 找到 UDT 的 null 实例时,UDT 会使用普通的 null 处理方法持久保存。 如果服务器不必序列化或反序列化 UDT,它不会在这些方面浪费时间;此外,它也不会浪费空间来存储 Null UDT。 每次从 CLR 引入 UDT 时,都会对 null 执行此检查,这意味着使用 Transact-SQL IS NULL 构造检查 NULL UDT 应始终正常工作。 服务器 还使用 IsNull 属性来测试实例是否为 null。 一旦服务器确定 UDT 为 Null,它便可以使用其本机 Null 处理方法。

IsNullget() 方法绝不是特殊情况。 如果 Point 变量@pNull,则默认情况下,@p.IsNull 的计算结果为“NULL”,而不是“1”。 这是因为 IsNull get() 方法的 SqlMethod(OnNullCall) 属性默认为 false。 由于该对象为 Null,因此当请求该属性时,不会反序列化该对象,则不调用该方法,并返回默认值“NULL”。

示例

在下面的示例中,is_Null 变量是私有的并且保存了 UDT 实例的 Null 状态。 您的代码必须保留 is_Null 的相应值。 UDT 还必须具有一个名为 Null 的静态属性,该属性返回 UDT 的 null 值实例。 这样,如果该实例在数据库中确实为 Null,UDT 便可返回 Null 值。

Private is_Null As Boolean  
  
Public ReadOnly Property IsNull() As Boolean _  
   Implements INullable.IsNull  
    Get  
        Return (is_Null)  
    End Get  
End Property  
  
Public Shared ReadOnly Property Null() As Point  
    Get  
        Dim pt As New Point  
        pt.is_Null = True  
        Return (pt)  
    End Get  
End Property  
private bool is_Null;  
  
public bool IsNull  
{  
    get  
    {  
        return (is_Null);  
    }  
}  
  
public static Point Null  
{  
    get  
    {  
        Point pt = new Point();  
        pt.is_Null = true;  
        return pt;  
    }  
}  

IS NULL 与 IsNull 的比较

请考虑包含架构点(id int、location Point)、点是 CLR UDT 的表,以及以下查询:

--Query 1  
SELECT ID  
FROM Points  
WHERE NOT (location IS NULL) -- Or, WHERE location IS NOT NULL;  
--Query 2:  
SELECT ID  
FROM Points  
WHERE location.IsNull = 0;  

这两个查询都返回具有非 Null 位置的点 ID。 在 Query 1 中,使用的是正常 Null 处理方法,不需要反序列化 UDT。 另一方面,查询 2 必须反序列化每个非 Null 对象并调用 CLR 以获取 IsNull 属性的值。 显然,使用 IS NULL 的性能会更好,从 Transact-SQL 代码读取 UDT 的 IsNull 属性不应该有理由。

那么,IsNull 属性的使用是什么? 首先,需要从 CLR 代码中确定值是否为 Null 。 其次,服务器需要一种方法来测试实例是否为 Null,因此服务器使用此属性。 确定其为 Null 后,可以使用其本机 null 处理来处理它。

实现 Parse 方法

Parse ToString 方法允许从 UDT 的字符串表示形式进行转换。 Parse 方法允许将字符串转换为 UDT。 它必须声明为静态(或在 Visual Basic 中共享),并采用 System.Data.SqlTypes.SqlString 类型的参数。

以下代码实现 UDT 的 Parse 方法,该方法分隔了 X 和 Y 坐标。 Parse 方法具有 System.Data.SqlTypes.SqlString 类型的单个参数,并假定 X 和 Y 值以逗号分隔的字符串的形式提供。 将 Microsoft.SqlServer.Server.SqlMethodAttribute.OnNullCall 属性设置为 false 可防止从 Point 的 null 实例调用 Parse 方法。

<SqlMethod(OnNullCall:=False)> _  
Public Shared Function Parse(ByVal s As SqlString) As Point  
    If s.IsNull Then  
        Return Null  
    End If  
  
    ' Parse input string here to separate out points.  
    Dim pt As New Point()  
    Dim xy() As String = s.Value.Split(",".ToCharArray())  
    pt.X = Int32.Parse(xy(0))  
    pt.Y = Int32.Parse(xy(1))  
    Return pt  
End Function  
[SqlMethod(OnNullCall = false)]  
public static Point Parse(SqlString s)  
{  
    if (s.IsNull)  
        return Null;  
  
    // Parse input string to separate out points.  
    Point pt = new Point();  
    string[] xy = s.Value.Split(",".ToCharArray());  
    pt.X = Int32.Parse(xy[0]);  
    pt.Y = Int32.Parse(xy[1]);  
    return pt;  
}  

实现 ToString 方法

ToString 方法将 UDT 转换为字符串值。 在这种情况下,将为 Point 类型的 Null 实例返回字符串“NULL”。 ToString 方法通过使用 System.Text.StringBuilder 返回由 X 和 Y 坐标值组成的逗号分隔的 System.String 来反转 Parse 方法。 由于 InvokeIfReceiverIsNull 默认为 false,因此不需要检查 Pointnull 实例。

Private _x As Int32  
Private _y As Int32  
  
Public Overrides Function ToString() As String  
    If Me.IsNull Then  
        Return "NULL"  
    Else  
        Dim builder As StringBuilder = New StringBuilder  
        builder.Append(_x)  
        builder.Append(",")  
        builder.Append(_y)  
        Return builder.ToString  
    End If  
End Function  
private Int32 _x;  
private Int32 _y;  
  
public override string ToString()  
{  
    if (this.IsNull)  
        return "NULL";  
    else  
    {  
        StringBuilder builder = new StringBuilder();  
        builder.Append(_x);  
        builder.Append(",");  
        builder.Append(_y);  
        return builder.ToString();  
    }  
}  

公开 UDT 属性

UDT 公开作为 System.Int32 类型的公共读写属性实现的 X 和 Y 坐标。

Public Property X() As Int32  
    Get  
        Return (Me._x)  
    End Get  
  
    Set(ByVal Value As Int32)  
        _x = Value  
    End Set  
End Property  
  
Public Property Y() As Int32  
    Get  
        Return (Me._y)  
    End Get  
  
    Set(ByVal Value As Int32)  
        _y = Value  
    End Set  
End Property  
public Int32 X  
{  
    get  
    {  
        return this._x;  
    }  
    set   
    {  
        _x = value;  
    }  
}  
  
public Int32 Y  
{  
    get  
    {  
        return this._y;  
    }  
    set  
    {  
        _y = value;  
    }  
}  

验证 UDT 值

使用 UDT 数据时,SQL Server 数据库引擎会自动将二进制值转换为 UDT 值。 该转换过程包括检查值是否符合类型的序列化格式和确保可以正确地反序列化值。 这可确保可以将值转换回二进制形式。 对于采用字节顺序的 UDT,这样还可以确保产生的二进制值与原始二进制值匹配。 这会防止在数据库中保留无效值。 在某些情况下,只进行这种级别的检查可能是不够的。 当要求 UDT 值在预期的域或范围之内时可能需要进行其他验证。 例如,实现日期的 UDT 可能要求日值为特定的有效值范围内的正数。

Microsoft.SqlServer.Server.SqlUserDefinedTypeAttribute.ValidationMethodName 属性的 Microsoft.SqlServer.Server.SqlUserDefinedTypeAttribute 允许提供在将数据分配给 UDT 或转换为 UDT 时服务器运行的验证方法的名称。 在运行 bcp 实用工具、BULK INSERT、DBCC CHECKDB、DBCC CHECKFILEGROUP、DBCC CHECKFILEGROUP、分布式查询和表格数据流(TDS)远程过程调用(RPC)操作期间,也会调用 ValidationMethodName 。 ValidationMethodName默认值为 null,指示没有验证方法。

示例

以下代码片段显示了 Point 类的声明,该声明指定 ValidatePointValidationMethodName

<Serializable(), SqlUserDefinedTypeAttribute(Format.Native, _  
  IsByteOrdered:=True, _  
  ValidationMethodName:="ValidatePoint")> _  
  Public Structure Point  
[Serializable]  
[Microsoft.SqlServer.Server.SqlUserDefinedType(Format.Native,  
  IsByteOrdered=true,   
  ValidationMethodName = "ValidatePoint")]  
public struct Point : INullable  
{  

如果指定了验证方法,则必须具有如下面的代码段所示的签名。

Private Function ValidationFunction() As Boolean  
    If (validation logic here) Then  
        Return True  
    Else  
        Return False  
    End If  
End Function  
private bool ValidationFunction()  
{  
    if (validation logic here)  
    {  
        return true;  
    }  
    else  
    {  
        return false;  
    }  
}  

验证方法可以具有任何范围,如果值有效,则返回 true;否则返回 false 如果方法返回 false 或引发异常,则该值被视为无效且引发错误。

在下面的示例中,代码仅允许零值或大于 X 和 Y 坐标的值。

Private Function ValidatePoint() As Boolean  
    If (_x >= 0) And (_y >= 0) Then  
        Return True  
    Else  
        Return False  
    End If  
End Function  
private bool ValidatePoint()  
{  
    if ((_x >= 0) && (_y >= 0))  
    {  
        return true;  
    }  
    else  
    {  
        return false;  
    }  
}  

验证方法限制

服务器在执行转换时调用验证方法,而不是当通过设置单个属性插入数据或使用 Transact-SQL INSERT 语句插入数据时调用验证方法。

如果希望验证方法在所有情况下执行,则必须从属性 setter 和 Parse 方法显式调用验证方法。 并不要求这样做,在某些情况下可能甚至不需要这样做。

Parse 验证示例

若要确保在 Point 类中调用 ValidatePoint 方法,必须从 Parse 方法以及设置 X 和 Y 坐标值的属性过程调用该方法。 以下代码片段演示如何从 Parse 函数调用 ValidatePoint 验证方法。

<SqlMethod(OnNullCall:=False)> _  
Public Shared Function Parse(ByVal s As SqlString) As Point  
    If s.IsNull Then  
        Return Null  
    End If  
  
    ' Parse input string here to separate out points.  
    Dim pt As New Point()  
    Dim xy() As String = s.Value.Split(",".ToCharArray())  
    pt.X = Int32.Parse(xy(0))  
    pt.Y = Int32.Parse(xy(1))  
  
    ' Call ValidatePoint to enforce validation  
    ' for string conversions.  
    If Not pt.ValidatePoint() Then  
        Throw New ArgumentException("Invalid XY coordinate values.")  
    End If  
    Return pt  
End Function  
[SqlMethod(OnNullCall = false)]  
public static Point Parse(SqlString s)  
{  
    if (s.IsNull)  
        return Null;  
  
    // Parse input string to separate out points.  
    Point pt = new Point();  
    string[] xy = s.Value.Split(",".ToCharArray());  
    pt.X = Int32.Parse(xy[0]);  
    pt.Y = Int32.Parse(xy[1]);  
  
    // Call ValidatePoint to enforce validation  
    // for string conversions.  
    if (!pt.ValidatePoint())   
        throw new ArgumentException("Invalid XY coordinate values.");  
    return pt;  
}  

属性验证示例

以下代码片段演示如何从设置 X 和 Y 坐标的属性过程调用 ValidatePoint 验证方法。

Public Property X() As Int32  
    Get  
        Return (Me._x)  
    End Get  
  
    Set(ByVal Value As Int32)  
        Dim temp As Int32 = _x  
        _x = Value  
        If Not ValidatePoint() Then  
            _x = temp  
            Throw New ArgumentException("Invalid X coordinate value.")  
        End If  
    End Set  
End Property  
  
Public Property Y() As Int32  
    Get  
        Return (Me._y)  
    End Get  
  
    Set(ByVal Value As Int32)  
        Dim temp As Int32 = _y  
        _y = Value  
        If Not ValidatePoint() Then  
            _y = temp  
            Throw New ArgumentException("Invalid Y coordinate value.")  
        End If  
    End Set  
End Property  
    public Int32 X  
{  
    get  
    {  
        return this._x;  
    }  
    // Call ValidatePoint to ensure valid range of Point values.  
    set   
    {  
        Int32 temp = _x;  
        _x = value;  
        if (!ValidatePoint())  
        {  
            _x = temp;  
            throw new ArgumentException("Invalid X coordinate value.");  
        }  
    }  
}  
  
public Int32 Y  
{  
    get  
    {  
        return this._y;  
    }  
    set  
    {  
        Int32 temp = _y;  
        _y = value;  
        if (!ValidatePoint())  
        {  
            _y = temp;  
            throw new ArgumentException("Invalid Y coordinate value.");  
        }  
    }  
}  

UDT 方法编码

编写 UDT 方法时,请考虑所用的算法是否可能会随时间的推移而改变。 如果可能改变的话,最好考虑为您的 UDT 所用的方法创建一个单独的类。 如果算法发生更改,则可以使用新代码重新编译类,并将程序集加载到 SQL Server 中,而不会影响 UDT。 在许多情况下,可以使用 Transact-SQL ALTER ASSEMBLY 语句重新加载 UDT,但这可能会导致现有数据出现问题。 例如,AdventureWorks 示例数据库附带的 Currency UDT 使用 ConvertCurrency 函数转换货币值,该函数在单独的类中实现。 在未来,转换算法可能会发生不可预知的变化,或者可能需要新的功能。 规划将来的更改时, 将 ConvertCurrency 函数与 Currency UDT 实现分离可提供更大的灵活性。

示例

Point 类包含三种用于计算距离的简单方法:DistanceDistanceFromDistanceFromXY。 每个值返回一个双精度值,计算从到零的距离、从指定点到的距离,以及从指定的 X 和 Y 坐标到点距离。 距离DistanceFrom 每个调用 DistanceFromXY,并演示如何对每个方法使用不同的参数。

' Distance from 0 to Point.  
<SqlMethod(OnNullCall:=False)> _  
Public Function Distance() As Double  
    Return DistanceFromXY(0, 0)  
End Function  
  
' Distance from Point to the specified point.  
<SqlMethod(OnNullCall:=False)> _  
Public Function DistanceFrom(ByVal pFrom As Point) As Double  
    Return DistanceFromXY(pFrom.X, pFrom.Y)  
End Function  
  
' Distance from Point to the specified x and y values.  
<SqlMethod(OnNullCall:=False)> _  
Public Function DistanceFromXY(ByVal ix As Int32, ByVal iy As Int32) _  
    As Double  
    Return Math.Sqrt(Math.Pow(ix - _x, 2.0) + Math.Pow(iy - _y, 2.0))  
End Function  
// Distance from 0 to Point.  
[SqlMethod(OnNullCall = false)]  
public Double Distance()  
{  
    return DistanceFromXY(0, 0);  
}  
  
// Distance from Point to the specified point.  
[SqlMethod(OnNullCall = false)]  
public Double DistanceFrom(Point pFrom)  
{  
    return DistanceFromXY(pFrom.X, pFrom.Y);  
}  
  
// Distance from Point to the specified x and y values.  
[SqlMethod(OnNullCall = false)]  
public Double DistanceFromXY(Int32 iX, Int32 iY)  
{  
    return Math.Sqrt(Math.Pow(iX - _x, 2.0) + Math.Pow(iY - _y, 2.0));  
}  

使用 SqlMethod 属性

Microsoft.SqlServer.Server.SqlMethodAttribute 类提供自定义属性,可用于标记方法定义,以便指定确定性、对 null 调用行为,以及指定方法是否为 mutator。 使用的是这些属性的默认值,仅在需要非默认值时才使用自定义特性。

注意

SqlMethodAttribute 类继承自 SqlFunctionAttribute 类,因此 SqlMethodAttributeSqlFunctionAttribute 继承 FillRowMethodNameTableDefinition 字段。 这意味着可以编写一个不属于此情况的表值方法。 该方法编译和程序集部署,但在运行时引发有关 IEnumerable 返回类型的错误,并显示以下消息:“程序集”<>中的类“class>”中的方法、属性或字段“<<name>”具有无效的返回类型。

下表介绍了一些相关的 Microsoft.SqlServer.Server.SqlMethodAttribute 属性,这些属性可用于 UDT 方法,并列出其默认值。

DataAccess
指示函数是否涉及访问存储在 SQL Server 本地实例中的用户数据。 默认值为 DataAccessKind

IsDeterministic
指示在输入值相同且数据库状态相同的情况下函数是否会生成相同的输出值。 默认值为 false

IsMutator
指示方法是否在 UDT 实例中引起状态变化。 默认值为 false

IsPrecise
指示函数是否涉及不精确的计算,如浮点运算。 默认值为 false

OnNullCall
指示在指定 Null 引用输入参数时是否调用此方法。 默认值为 true

示例

Microsoft.SqlServer.Server.SqlMethodAttribute.IsMutator 属性允许标记允许更改 UDT 实例状态的方法。 Transact-SQL 不允许在一个 UPDATE 语句的 SET 子句中设置两个 UDT 属性。 然而,您可以将一个方法标记为更改两个成员的赋值函数。

注意

在查询中不允许使用赋值函数方法。 这些方法只能在赋值语句或数据修改语句中调用。 如果标记为 mutator 的方法未返回 void (或不是 Visual Basic 中的 Sub ),CREATE TYPE 将失败并出现错误。

以下语句假定存在具有 Rotate 方法的三角形 UDT。 以下 Transact-SQL 更新语句调用 Rotate 方法:

UPDATE Triangles SET t.RotateY(0.6) WHERE id=5  

Rotate 方法使用 SqlMethod 属性设置 IsMutator 修饰为 true,以便 SQL Server 可以将该方法标记为 mutator 方法。 该代码还会将 OnNullCall 设置为 false,该参数指示如果任何输入参数为空引用,则该方法将返回 null 引用(Visual Basic 中的 Nothing )。

<SqlMethod(IsMutator:=True, OnNullCall:=False)> _  
Public Sub Rotate(ByVal anglex as Double, _  
  ByVal angley as Double, ByVal anglez As Double)   
   RotateX(anglex)  
   RotateY(angley)  
   RotateZ(anglez)  
End Sub  
[SqlMethod(IsMutator = true, OnNullCall = false)]  
public void Rotate(double anglex, double angley, double anglez)   
{  
   RotateX(anglex);  
   RotateY(angley);  
   RotateZ(anglez);  
}  

采用用户定义的格式实现 UDT

使用用户定义的格式实现 UDT 时,必须实现 实现 Microsoft.SqlServer.Server.IBinarySerialize 接口的读 方法,以处理序列化和反序列化 UDT 数据。 还必须指定 Microsoft.SqlServer.Server.SqlUserDefinedTypeAttributeMaxByteSize 属性。

Currency UDT

货币 UDT 包含在可随 SQL Server 一起安装的 CLR 示例,从 SQL Server 2005 (9.x) 开始。

货币 UDT 支持处理特定文化的货币系统中的货币金额。 必须定义两个字段:CultureInfo字符串,该字符串指定谁颁发了货币(例如 en-us)和 CurrencyValue 的小数(货币金额)。

尽管服务器不使用它来执行比较, 但 Currency UDT 实现 System.IComparable 接口,该接口公开单个方法 System.IComparable.CompareTo。 在需要精确比较或排序区域性中的货币值的情况下,在客户端会使用此方法。

在 CLR 中运行的代码将区域性与货币值分开比较。 对于 Transact-SQL 代码,以下操作确定比较:

  1. IsByteOrdered 属性设置为 true,该属性指示 SQL Server 使用磁盘上的持久二进制表示形式进行比较。

  2. 使用货币 UDT 的 Write 方法确定 UDT 在磁盘上如何持久保存,因此如何比较和排序 UDT 值,以便执行 Transact-SQL 操作。

  3. 使用以下二进制格式保存货币 UDT:

    1. 将区域性另存为 UTF-16 编码的字符串并用第 0-19 个字节表示,右侧以 Null 字符填充。

    2. 使用第 20 个字节及以后的字节包含货币的十进制值。

填充的目的是确保区域性与货币值完全分离,以便当一个 UDT 与 Transact-SQL 代码中的另一个 UDT 进行比较时,将区域性字节与区域性字节进行比较,而货币字节值与货币字节值进行比较。

Currency 属性

货币 UDT 使用以下属性定义。

<Serializable(), Microsoft.SqlServer.Server.SqlUserDefinedType( _  
    Microsoft.SqlServer.Server.Format.UserDefined, _  
    IsByteOrdered:=True, MaxByteSize:=32), _  
    CLSCompliant(False)> _  
Public Structure Currency  
Implements INullable, IComparable, _  
Microsoft.SqlServer.Server.IBinarySerialize  
[Serializable]  
[SqlUserDefinedType(Format.UserDefined,   
    IsByteOrdered = true, MaxByteSize = 32)]  
    [CLSCompliant(false)]  
    public struct Currency : INullable, IComparable, IBinarySerialize  
    {  

创建带有 IBinarySerialize 的 Read 和 Write 方法

选择 用户定义的 序列化格式时,还必须实现 IBinarySerialize 接口并创建自己的 读取写入 方法。 Currency UDT 中的以下过程使用 System.IO.BinaryReaderSystem.IO.BinaryWriter 从 UDT 读取和写入 UDT。

' IBinarySerialize methods  
' The binary layout is as follow:  
'    Bytes 0 - 19: Culture name, padded to the right with null  
'    characters, UTF-16 encoded  
'    Bytes 20+: Decimal value of money  
' If the culture name is empty, the currency is null.  
Public Sub Write(ByVal w As System.IO.BinaryWriter) _  
  Implements Microsoft.SqlServer.Server.IBinarySerialize.Write  
    If Me.IsNull Then  
        w.Write(nullMarker)  
        w.Write(System.Convert.ToDecimal(0))  
        Return  
    End If  
  
    If cultureName.Length > cultureNameMaxSize Then  
        Throw New ApplicationException(String.Format(CultureInfo.CurrentUICulture, _  
           "{0} is an invalid culture name for currency as it is too long.", cultureNameMaxSize))  
    End If  
  
    Dim paddedName As String = cultureName.PadRight(cultureNameMaxSize, CChar(vbNullChar))  
  
    For i As Integer = 0 To cultureNameMaxSize - 1  
        w.Write(paddedName(i))  
    Next i  
  
    ' Normalize decimal value to two places  
    currencyVal = Decimal.Floor(currencyVal * 100) / 100  
    w.Write(currencyVal)  
End Sub  
  
Public Sub Read(ByVal r As System.IO.BinaryReader) _  
  Implements Microsoft.SqlServer.Server.IBinarySerialize.Read  
    Dim name As Char() = r.ReadChars(cultureNameMaxSize)  
    Dim stringEnd As Integer = Array.IndexOf(name, CChar(vbNullChar))  
  
    If stringEnd = 0 Then  
        cultureName = Nothing  
        Return  
    End If  
  
    cultureName = New String(name, 0, stringEnd)  
    currencyVal = r.ReadDecimal()  
End Sub  
  
// IBinarySerialize methods  
// The binary layout is as follow:  
//    Bytes 0 - 19:Culture name, padded to the right   
//    with null characters, UTF-16 encoded  
//    Bytes 20+:Decimal value of money  
// If the culture name is empty, the currency is null.  
public void Write(System.IO.BinaryWriter w)  
{  
    if (this.IsNull)  
    {  
        w.Write(nullMarker);  
        w.Write((decimal)0);  
        return;  
    }  
  
    if (cultureName.Length > cultureNameMaxSize)  
    {  
        throw new ApplicationException(string.Format(  
            CultureInfo.InvariantCulture,   
            "{0} is an invalid culture name for currency as it is too long.",   
            cultureNameMaxSize));  
    }  
  
    String paddedName = cultureName.PadRight(cultureNameMaxSize, '\0');  
    for (int i = 0; i < cultureNameMaxSize; i++)  
    {  
        w.Write(paddedName[i]);  
    }  
  
    // Normalize decimal value to two places  
    currencyValue = Decimal.Floor(currencyValue * 100) / 100;  
    w.Write(currencyValue);  
}  
public void Read(System.IO.BinaryReader r)  
{  
    char[] name = r.ReadChars(cultureNameMaxSize);  
    int stringEnd = Array.IndexOf(name, '\0');  
  
    if (stringEnd == 0)  
    {  
        cultureName = null;  
        return;  
    }  
  
    cultureName = new String(name, 0, stringEnd);  
    currencyValue = r.ReadDecimal();  
}  

另请参阅

创建用户定义类型