建立使用者定義型別 - 編碼

適用於:SQL Server

當您編碼使用者定義型別 (UDT) 定義時,您必須根據您是否將 UDT 實作為類別或結構以及您已選擇的格式和序列化選項來實作各種功能。

本節中的範例說明如何在 Visual Basic) 中將 Point UDT 實作為 結構 (或 結構Point 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會將Point UDT 的儲存格式設定為NativeIsByteOrdered設定為true,這可確保比較的結果在SQL Server中相同,就像在 Managed 程式碼中發生相同的比較一樣。 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 vs. IsNull

請考慮資料表,其中包含架構 Points (id int、location Point) 、 其中 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 位置之點的識別碼。 查詢 1 會使用正常 null 處理,而且不需要還原序列化 UDT。 另一方面,查詢 2 必須還原序列化每個非Null 物件,並呼叫 CLR 以取得 IsNull 屬性的值。 很明顯地,使用 IS Null 會展現更好的效能,而且絕對不應該有理由從 Transact-SQL 程式碼讀取 UDT 的 IsNull 屬性。

那麼, IsNull 屬性的使用為何? 首先,從 CLR 程式碼中判斷值是否為 Null 。 其次,伺服器需要一種方式來測試實例是否為 Null,因此伺服器會使用此屬性。 判斷它為 Null之後,就可以使用其原生 Null 處理來處理它。

實作 Parse 方法

ParseToString方法允許從 UDT 的字串表示來回轉換。 Parse方法允許將字串轉換成 UDT。 它必須在 Visual Basic) 中宣告為 靜態 (或 共用 ,並採用 System.Data.SqlTypes.SqlString類型的參數。

下列程式碼會實作Point 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方法會將Point UDT 轉換為字串值。 在此情況下, 會針對 Point 類型的 Null 實例傳回字串 「Null」。 ToString方法會使用System.Text.StringBuilder來傳回由 X 和 Y 座標值組成的逗號分隔System.String,以反轉Parse方法。 由於 InvokeIfReceiverIsNull 預設為 false,因此不需要檢查 Point 的 Null 實例。

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 屬性

Point UDT 會公開 X 和 Y 座標,這些座標會實作為System.Int32類型的公用讀寫屬性。

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 Database Engine 會自動將二進位值轉換成 UDT 值。 此轉換程序包括檢查適用於此類型之序列化格式的值,並確保可正確地將此值還原序列化。 如此可確保此值可以轉換回二進位格式。 如果是依照位元組排序的 UDT,這也可確保產生的二進位值會符合原始二進位值。 如此可防止在資料庫中保留無效的值。 在某些情況下,這個層級的檢查可能不夠。 當要求 UDT 值位於預期的網域或範圍內時,可能需要其他驗證。 例如,實作日期的 UDT 可能會要求日期值是一個正數,而且在某個有效值的範圍內。

Microsoft.SqlServer.Server.SqlUserDefinedTypeAttribute.ValidationMethodName屬性的Microsoft.SqlServer.Server.SqlUserDefinedTypeAttribute可讓您提供當資料指派給 UDT 或轉換成 UDT 時,伺服器執行之驗證方法的名稱。 在執行 bcp 公用程式、BULK INSERT、DBCC CHECKDB、DBCC CHECKFILEGROUP、DBCC CHECKTABLE、分散式查詢和表格式資料流程時,也會呼叫ValidationMethodName (TDS) 遠端程序呼叫 (RPC) 作業。 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;  
}  

Property 驗證範例

下列程式碼片段示範如何從設定 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。 在許多情況下,UDT 可以使用 Transact-SQL ALTER ASSEMBLY 語句重載,但這可能會導致現有資料發生問題。 例如,AdventureWorks範例資料庫隨附的Currency UDT 會使用ConvertCurrency函式來轉換貨幣值,這會在個別類別中實作。 轉換演算法在未來有可能以非預期的方式變更,或者可能需要新的功能。 在規劃未來變更時, 將 ConvertCurrency 函式與 Currency UDT 實作分開可提供更大的彈性。

範例

Point類別包含三種計算距離的簡單方法:DistanceDistanceFrom 和 DistanceFromXY 每個都會傳回 一個雙精度 浮點計算 從 Point 到零的距離、從指定的點到 Point的距離,以及從指定的 X 和 Y 座標到 Point的距離。 DistanceDistanceFrom 每個呼叫 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。 已假設這些屬性 (Property) 的預設值,而且只有在需要非預設值時才會使用自訂屬性 (Attribute)。

注意

SqlMethodAttribute類別繼承自SqlFunctionAttribute類別,因此SqlMethodAttribute繼承SqlFunctionAttributeFillRowMethodNameTableDefinition欄位。 這意味著撰寫資料表值方法是可行的,不過情況不是這樣。 方法會編譯和元件部署,但在執行時間引發有關 IEnumerable傳回類型的錯誤,訊息如下:「元件 'assembly > ' 類別 'class > ' 中的方法、屬性或欄位 < ' << name > ' 有不正確傳回類型。」

下表描述一些可用於 UDT 方法的相關 Microsoft.SqlServer.Server.SqlMethodAttribute 屬性,並列出其預設值。

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,這樣會變更兩個成員。

注意

查詢中不允許使用 Mutator 方法。 它們只能在指派陳述式或資料修改陳述式中呼叫。 如果標示為 mutator 的方法不會傳回 void (,或不是 Visual Basic) 中的 Sub ,CREATE TYPE 會失敗併發生錯誤。

下列語句假設有Rotate方法的三角形UDT 存在。 下列 Transact-SQL update 語句會叫用 Rotate 方法:

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

Rotate方法會使用SqlMethod屬性將IsMutator設定為true裝飾,讓SQL Server可以將方法標示為 Mutator 方法。 此程式碼也會將 OnNullCall 設定為 false,這表示如果有任何輸入參數為 Null 參考,此方法會在 Visual Basic 中傳回 Null 參考 (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. 使用Currency UDT 的Write方法,判斷 UDT 如何保存在磁片上,因此 UDT 值如何比較及排序以用於 Transact-SQL 作業。

  3. 使用下列二進位格式儲存 貨幣 UDT:

    1. 針對位元組 0-19 將文化特性儲存為 UTF-16 編碼的字串,並使用 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 介面,並建立您自己的 讀取寫入 方法。 下列來自貨幣UDT 的程式會使用System.IO.BinaryReaderSystem.IO.BinaryWriter來讀取和寫入 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();  
}  

另請參閱

建立User-Defined類型