# Strukturierte Daten

> Anleitung: Auf strukturierte Datentypen zugreifen, einfach, namenbasiert, dynamisch oder typisiert, und Datentypen generieren und definieren.

**Beispielanwendungen:**

- Client.DataTypes ([C#](https://github.com/Traeger-GmbH/opcuanet-samples/tree/master/cs/Basic/Client.DataTypes), [VB.NET](https://github.com/Traeger-GmbH/opcuanet-samples/tree/master/vb/Basic/Client.DataTypes)): Liest Nodes mit strukturierten Datentypen des Servers und arbeitet mit ihnen über eigene .NET-Klassen.
- Server.DataTypes ([C#](https://github.com/Traeger-GmbH/opcuanet-samples/tree/master/cs/Basic/Server.DataTypes), [VB.NET](https://github.com/Traeger-GmbH/opcuanet-samples/tree/master/vb/Basic/Server.DataTypes)): Stellt Nodes bereit, deren Werte eigene strukturierte Datentypen und Aufzählungen haben.

Die folgenden Abschnitte gehen davon aus, dass der Server einen Node mit der NodeId `ns=2;s=Machine/Operator` bereitstellt. Dieser Node verwendet den fiktiven Datentyp **`StaffType`**.
Der Datentyp hat die folgende Struktur:

```
StaffType
  .Name : string
  .ID : int
  .Shift : ShiftInfoType
    .Name : string
    .Elapsed : DateTime
    .Remaining : byte
```

## Einfachster Zugriff mit dynamic

Beteiligte Typen: `OpcClient`, `OpcNodeId`, `OpcValue`, `OpcDataObject`

Für den einfachen Zugriff auf Werte von Variablen-Nodes mit strukturierten Daten unterstützt das OPC UA .NET SDK das Schlüsselwort `dynamic`. Zugriffe auf Variablen, die mit `dynamic` deklariert sind, wertet .NET zur Laufzeit aus. Sie greifen damit auf die Daten eines strukturierten Datentyps zu, ohne vorher einen .NET-Typ zu implementieren. So sieht ein solcher Zugriff aus:

```csharp
client.UseDynamic = true;
client.Connect();
 
dynamic staff = client.ReadNode("ns=2;s=Machine/Operator").Value;
 
// Access the 'Name' and 'ID' field of the data without to declare the data type itself.
// Just use the field names known as they would be defined in a .NET Type.
Console.WriteLine("Name: {0}", staff.Name);
Console.WriteLine("Staff ID: {0}", staff.ID);
 
// Continue accessing subsequently used data types.
Console.WriteLine("Shift: {0}", staff.Shift.Name);
Console.WriteLine("- Time Elapsed: {0}", staff.Shift.Elapsed);
Console.WriteLine("- Jobs Remaining: {0}", staff.Shift.Remaining);
 
// Change Shift
staff.Name = "John";
staff.ID = 4242;
staff.Shift.Name = "Swing Shift";
 
client.WriteNode("ns=2;s=Machine/Operator", staff);

```

```vbnet
client.UseDynamic = True
client.Connect()

Dim staff = client.ReadNode("ns=2;s=Machine/Operator").Value

'Access the 'Name' and 'ID' field of the data without to declare the data type itself.
'Just use the field names known as they would be defined in a .NET Type.
Console.WriteLine("Name: {0}", staff.Name)
Console.WriteLine("Staff ID: {0}", staff.ID)

'Continue accessing subsequently used data types.
Console.WriteLine("Shift: {0}", staff.Shift.Name)
Console.WriteLine("- Time Elapsed: {0}", staff.Shift.Elapsed)
Console.WriteLine("- Jobs Remaining: {0}", staff.Shift.Remaining)

'Change Shift
staff.Name = "John"
staff.ID = 4242
staff.Shift.Name = "Swing Shift"

client.WriteNode("ns=2;s=Machine/Operator", staff)

```

## Namen-basierter Zugriff mit OpcDataObject

Beteiligte Typen: `OpcClient`, `OpcNodeId`, `OpcValue`, `OpcDataObject`, `OpcDataField`

Für den Namen-basierten Zugriff auf Werte von Variablen-Nodes mit strukturierten Daten verwenden Sie direkt die Klasse `OpcDataObject`. Wenn Sie die Namen der Felder des Datentyps kennen, greifen Sie so auf die Felder zu:

```csharp
client.UseDynamic = true;
client.Connect();
 
OpcDataObject staff = client.ReadNode("ns=2;s=Machine/Operator").As<OpcDataObject>();
 
// Access the 'Name' and 'ID' field of the data without to declare the data type itself.
// Just use the field names known as the 'key' to access the according field value.
Console.WriteLine("Name: {0}", staff["Name"].Value);
Console.WriteLine("Staff ID: {0}", staff["ID"].Value);
 
// Continue accessing subsequently used data types using the OpcDataObject as before.
OpcDataObject shift = (OpcDataObject)staff["Shift"].Value;
 
Console.WriteLine("Shift: {0}", shift["Name"].Value);
Console.WriteLine("- Time Elapsed: {0}", shift["Elapsed"].Value);
Console.WriteLine("- Jobs Remaining: {0}", shift["Remaining"].Value);
 
// Change Shift
staff["Name"].Value = "John";
staff["ID"].Value = 4242;
shift["Name"].Value = "Swing Shift";
 
client.WriteNode("ns=2;s=Machine/Operator", staff);

```

```vbnet
client.UseDynamic = True
client.Connect()

Dim staff As OpcDataObject = client.ReadNode("ns=2;s=Machine/Operator").As(Of OpcDataObject)()

'Access the 'Name' and 'ID' field of the data without to declare the data type itself.
'Just use the field names known as the 'key' to access the according field value.
Console.WriteLine("Name: {0}", staff("Name").Value)
Console.WriteLine("Staff ID: {0}", staff("ID").Value)

'Continue accessing subsequently used data types using the OpcDataObject as before.
Dim shift As OpcDataObject = CType(staff("Shift").Value, OpcDataObject)

Console.WriteLine("Shift: {0}", shift("Name").Value)
Console.WriteLine("- Time Elapsed: {0}", shift("Elapsed").Value)
Console.WriteLine("- Jobs Remaining: {0}", shift("Remaining").Value)

'Change Shift
staff("Name").Value = "John"
staff("ID").Value = 4242
shift("Name").Value = "Swing Shift"

client.WriteNode("ns=2;s=Machine/Operator", staff)

```

## Typinformationen für den dynamischen Zugriff laden

Beteiligte Typen: `OpcClient`, `OpcNodeSet`, `OpcAutomatism`, `OpcDataTypeSystem`, `OpcRawObject`, `OpcData`

Für den Zugriff mit `dynamic` (Abschnitt [Einfachster Zugriff mit dynamic](https://www.traeger.de/docs/development/opcua/client/structured-data#einfachster-zugriff-mit-dynamic)) und den Namen-basierten Zugriff (Abschnitt [Namen-basierter Zugriff mit OpcDataObject](https://www.traeger.de/docs/development/opcua/client/structured-data#namen-basierter-zugriff-mit-opcdataobject)) benötigt der Client Informationen über die Datentypen des Servers. In Visual Basic entspricht `dynamic` einer nicht typisierten Deklaration mit `Dim`. Die Klasse `OpcClient` ruft diese Informationen beim Aufruf von `Connect()` automatisch vom Server ab, wenn Sie **zuvor** die Eigenschaft `UseDynamic` auf `true` setzen. Diese Eigenschaft gibt es auf der Instanz von `OpcClient` und auf der statischen Klasse `OpcAutomatism`.

Mit dieser Einstellung lädt der Client die notwendigen Typinformationen vom Server. Nach dem Aufruf von `Connect()` greifen Sie dann ohne eigenen Code für die Typen auf die Daten zu, wie in den vorherigen Abschnitten gezeigt.

Wenn eine `UANodeSet.xml` oder eine andere XML-Datei mit der Beschreibung des Servers vorliegt, können Sie diese Datei auch in die Clientanwendung laden. Der Inhalt einer solchen Datei beginnt mit `UANodeSet`. Der Client bezieht die Informationen dann aus dem NodeSet und muss die Typinformationen nicht beim Verbindungsaufbau abrufen. So laden Sie das NodeSet:

```csharp
using (var client = new OpcClient("opc.tcp://localhost:4840")) {
    client.NodeSet = OpcNodeSet.Load(@"..\Resources\MyServersNodeSet.xml");
    client.UseDynamic = true;
 
    client.Connect();
    dynamic staff = client.ReadNode("ns=2;s=Machine/Operator").Value;
 
    Console.WriteLine("Name: {0}", staff.Name);
    Console.WriteLine("Staff ID: {0}", staff.ID);
}
```

```vbnet
Using client = New OpcClient("opc.tcp://localhost:4840")
    client.NodeSet = OpcNodeSet.Load("..\Resources\MyServersNodeSet.xml")
    client.UseDynamic = True

    client.Connect()
    Dim staff = client.ReadNode("ns=2;s=Machine/Operator").Value

    Console.WriteLine("Name: {0}", staff.Name)
    Console.WriteLine("Staff ID: {0}", staff.ID)
End Using
```

Wenn ein NodeSet gesetzt ist, liefert die Methode `GetDataTypeSystem()` der Klasse `OpcClient` eine Instanz von `OpcDataTypeSystem`, die das Typsystem aus dem NodeSet beschreibt. Wenn Sie kein NodeSet über die Eigenschaft `NodeSet` festlegen, beschreibt die gelieferte Instanz das Typsystem des Servers. Die Informationen dazu fragt der Client bei Bedarf vom Server ab. In beiden Fällen setzt der Aufruf eine bestehende Verbindung zum Server voraus. Umfangreiche Typsysteme können die Grenzen der Datenübertragung überschreiten (Abschnitt [Grenzen der Datenübertragung](https://www.traeger.de/docs/development/opcua/client/configuration#grenzen-der-datenubertragung)).

**Unbekannte Strukturen**: Wenn der Client den Datentyp einer Struktur nicht kennt oder ein Feld keinen festen ValueRank hat, kann er den Wert nicht dekodieren. Er liefert ihn dann als `OpcRawObject`. Dessen Eigenschaft `Encoding` nennt die Kodierung (binär oder XML), die Eigenschaft `RawData` enthält den kodierten Inhalt. Mit der Methode `Decode<T>` dekodieren Sie den Inhalt, sobald der Typ bekannt ist. Umgekehrt kodiert `OpcData.Encode` eine Struktur als `OpcRawObject`.

## Typisierter Zugriff mit eigenen .NET-Typen

Beteiligte Typen: `OpcClient`, `OpcNodeId`, `OpcDataTypeAttribute`, `OpcDataTypeEncodingAttribute`

Für den typisierten Zugriff definieren Sie .NET-Typen in der Clientanwendung so, wie sie im Server definiert sind. Alle nötigen Metadaten stellen Attribute bereit. Typen mit diesen Attributen registriert das SDK standardmäßig automatisch beim Verbindungsaufbau. Abschalten lässt sich das über die Eigenschaft `OpcAutomatism.UseDynamicTypeRegistration`. So definieren Sie .NET-Typen für strukturierte Datentypen:

```csharp
[OpcDataType("ns=2;s=StaffType")]
[OpcDataTypeEncoding("ns=2;s=StaffType-Binary")]
public class Staff
{
    public string Name { get; set; }
    public int ID { get; set; }
    public ShiftInfo Shift { get; set; }
}
 
[OpcDataType("ns=2;s=ShiftInfoType")]
[OpcDataTypeEncoding("ns=2;s=ShiftInfoType-Binary")]
public class ShiftInfo
{
    public string Name { get; set; }
    public DateTime Elapsed { get; set; }
    public byte Remaining { get; set; }
}

```

```vbnet
<OpcDataType("ns=2;s=StaffType")>
<OpcDataTypeEncoding("ns=2;s=StaffType-Binary")>
Public Class Staff
    Public Property Name As String
    Public Property ID As Integer
    Public Property Shift As ShiftInfo
End Class

<OpcDataType("ns=2;s=ShiftInfoType")>
<OpcDataTypeEncoding("ns=2;s=ShiftInfoType-Binary")>
Public Class ShiftInfo
    Public Property Name As String
    Public Property Elapsed As DateTime
    Public Property Remaining As Byte
End Class

```

Wenn Sie clientseitig einen .NET-Typ für einen serverseitig definierten strukturierten Datentyp definiert haben, verwenden Sie ihn so:

```csharp
client.Connect();
Staff staff = client.ReadNode("ns=2;s=Machine/Operator").As<Staff>();
 
// Access the 'Name' and 'ID' field of the data with the declared the data type.
Console.WriteLine("Name: {0}", staff.Name);
Console.WriteLine("Staff ID: {0}", staff.ID);
 
// Continue accessing subsequently used data types.
Console.WriteLine("Shift: {0}", staff.Shift.Name);
Console.WriteLine("- Time Elapsed: {0}", staff.Shift.Elapsed);
Console.WriteLine("- Jobs Remaining: {0}", staff.Shift.Remaining);
 
// Change Shift
staff.Name = "John";
staff.ID = 4242;
staff.Shift.Name = "Swing Shift";
 
client.WriteNode("ns=2;s=Machine/Operator", staff);

```

```vbnet
client.Connect()
Dim staff As Staff = client.ReadNode("ns=2;s=Machine/Operator").As(Of Staff)()

'Access the 'Name' and 'ID' field of the data with the declared the data type.
Console.WriteLine("Name: {0}", staff.Name)
Console.WriteLine("Staff ID: {0}", staff.ID)

'Continue accessing subsequently used data types.
Console.WriteLine("Shift: {0}", staff.Shift.Name)
Console.WriteLine("- Time Elapsed: {0}", staff.Shift.Elapsed)
Console.WriteLine("- Jobs Remaining: {0}", staff.Shift.Remaining)

'Change Shift
staff.Name = "John"
staff.ID = 4242
staff.Shift.Name = "Swing Shift"

client.WriteNode("ns=2;s=Machine/Operator", staff)

```

## Datentypen generieren

Für den typisierten Zugriff (Abschnitt [Typisierter Zugriff mit eigenen .NET-Typen](https://www.traeger.de/docs/development/opcua/client/structured-data#typisierter-zugriff-mit-eigenen-net-typen)) können Sie einzelne oder alle Datentypen eines OPC UA Servers mit OPC Watch generieren (Seite [Downloads](https://www.traeger.de/docs/development/opcua/downloads)).

**Über den Server generieren**

So generieren Sie einen einzelnen Datentyp als .NET-Implementierung:

1. OPC Watch öffnen
2. Neue Verbindung anlegen (+) und bei Bedarf konfigurieren
3. Verbindung zum Server herstellen (Stecker-Symbol)
4. Einen Node auswählen:
   - entweder einen Variablen-Node mit einem strukturierten Datentyp als Wert
   - oder einen Datentyp-Node unter `/Types/DataTypes/BaseDataType/Structure` oder `/Enumeration`
5. Rechtsklick auf diesen Node
6. „Generate DataType“ klicken
7. Den Code in die Anwendung einfügen

Alle Datentypen generieren Sie wahlweise in eine Code-Datei (`*.cs`) oder eine Assembly-Datei (`*.dll`). So gehen Sie vor:

1. OPC Watch öffnen
2. Neue Verbindung anlegen (+) und bei Bedarf konfigurieren
3. Verbindung zum Server herstellen (Stecker-Symbol)
4. Den Server-Node auswählen, dessen Name mit `opc.tcp://` beginnt
5. Rechtsklick auf diesen Node
6. „Generate Models“ klicken
7. Im Dialog den gewünschten Dateityp auswählen
8. „Speichern“ klicken
9. Die Datei dem Projekt hinzufügen

**Über ein NodeSet generieren**

1. OPC Watch öffnen
2. In der oberen rechten Ecke der Anwendung das erste Symbol anklicken
3. NodeSet-Datei öffnen, meist eine XML-Datei einer Companion-Spezifikation
4. OPC Watch lädt das NodeSet und zeigt anschließend den Dialog „Speichern unter“ an
5. Im Dialog den gewünschten Dateityp auswählen
6. „Speichern“ klicken
7. Die Datei dem Projekt hinzufügen

## Datentypen definieren

Beteiligte Typen: `OpcClient`, `OpcNodeId`, `OpcData`, `OpcDataTypeAttribute`, `OpcDataTypeEncodingAttribute`, `OpcDataTypeSystem`, `OpcDataTypeInfo`

Statt den Datentyp als .NET-Code oder .NET-Assembly zu generieren (Abschnitt [Datentypen generieren](https://www.traeger.de/docs/development/opcua/client/structured-data#datentypen-generieren)), können Sie ihn auch manuell definieren. Implementieren Sie dazu den Typ so, wie der Server ihn definiert. Der .NET-Typ muss also die Felder des strukturierten Datentyps in genau derselben Reihenfolge und mit denselben Typen bereitstellen, egal ob er eine Struktur oder eine Klasse ist. Alle weiteren Metadaten stellen Attribute bereit:

```csharp
[OpcDataType("<NodeId of DataType Node>")]
[OpcDataTypeEncoding(
        "<NodeId of Binary Encoding Node>",
        NamespaceUri = "<NamespaceUri.Value of binary Dictionary-Node>")]
internal struct MyDataType
{
    public short FieldA;
    public int FieldB;
    public string FieldC;
    ...
}

```

```vbnet
<OpcDataType("<NodeId of DataType Node>")>
<OpcDataTypeEncoding( _
        "<NodeId of Binary Encoding Node>", _
        NamespaceUri:="<NamespaceUri.Value of binary Dictionary-Node>")>
Friend Structure MyDataType
    Public FieldA As Short
    Public FieldB As Integer
    Public FieldC As String
    ...
End Structure

```

Die nötigen Informationen erhalten Sie aus dem Handbuch des OPC UA Servers, vom Team, das die SPS programmiert, oder über die Klasse `OpcClient`. Mit `OpcClient` untersuchen Sie dazu den Variablen-Node, der den strukturierten Datentyp verwendet:

```csharp
OpcNodeInfo node = client.BrowseNode("ns=2;s=Machine/Operator");
 
if (node is OpcVariableNodeInfo variableNode) {
    OpcNodeId dataTypeId = variableNode.DataTypeId;
    OpcDataTypeInfo dataType = client.GetDataTypeSystem().GetType(dataTypeId);
 
    Console.WriteLine(dataType.TypeId);
    Console.WriteLine(dataType.Encoding);
 
    Console.WriteLine(dataType.Name);
 
    foreach (OpcDataFieldInfo field in dataType.GetFields())
        Console.WriteLine(".{0} : {1}", field.Name, field.FieldType);
 
    Console.WriteLine();        
    Console.WriteLine("Data Type Attributes:");
    Console.WriteLine(
            "\t[OpcDataType(\"{0}\")]",
            dataType.TypeId.ToString(OpcNodeIdFormat.Foundation));
    Console.WriteLine(
            "\t[OpcDataTypeEncoding(\"{0}\", NamespaceUri = \"{1}\")]",
            dataType.Encoding.Id.ToString(OpcNodeIdFormat.Foundation),
            dataType.Encoding.Namespace.Value);
}

```

```vbnet
Dim node As OpcNodeInfo = client.BrowseNode("ns=2;s=Machine/Operator")
Dim variableNode As OpcVariableNodeInfo = TryCast(node, OpcVariableNodeInfo)

If variableNode IsNot Nothing Then
    Dim dataTypeId As OpcNodeId = variableNode.DataTypeId
    Dim dataType As OpcDataTypeInfo = client.GetDataTypeSystem().GetType(dataTypeId)

    Console.WriteLine(dataType.TypeId)
    Console.WriteLine(dataType.Encoding)

    Console.WriteLine(dataType.Name)

    For Each field As OpcDataFieldInfo In dataType.GetFields()
        Console.WriteLine(".{0} : {1}", field.Name, field.FieldType)
    Next

    Console.WriteLine()
    Console.WriteLine("Data Type Attributes:")
    Console.WriteLine( _
            vbTab & "[OpcDataType(""{0}"")]", _
            dataType.TypeId.ToString(OpcNodeIdFormat.Foundation))
    Console.WriteLine( _
            vbTab & "[OpcDataTypeEncoding(""{0}"", NamespaceUri = ""{1}"")]", _
            dataType.Encoding.Id.ToString(OpcNodeIdFormat.Foundation), _
            dataType.Encoding.Namespace.Value)
End If

```

## Union-Datentypen

Beteiligte Typen: `OpcClient`, `OpcDataTypeAttribute`, `OpcDataTypeEncodingAttribute`, `IOpcUnion`

Ein Union-Datentyp überträgt von mehreren Feldern immer nur eines. Welches Feld übertragen wird, legt ein vorangestellter Auswahlwert fest. Um einen solchen Datentyp zu definieren, implementiert der .NET-Typ die Schnittstelle `IOpcUnion`. Ihre Eigenschaft `SwitchValue` enthält den Auswahlwert. Der Wert `1` wählt das erste Feld, der Wert `2` das zweite und so weiter. Beim Wert `0` wird kein Feld übertragen:

```csharp
[OpcDataType("<NodeId of DataType Node>")]
[OpcDataTypeEncoding(
        "<NodeId of Binary Encoding Node>",
        NamespaceUri = "<NamespaceUri.Value of binary Dictionary-Node>")]
internal class MyUnionDataType : IOpcUnion
{
    // 0 = no field, 1 = 'Number', 2 = 'Text'.
    public uint SwitchValue { get; set; }
 
    public int Number { get; set; }
 
    public string Text { get; set; }
}

```

```vbnet
<OpcDataType("<NodeId of DataType Node>")>
<OpcDataTypeEncoding( _
        "<NodeId of Binary Encoding Node>", _
        NamespaceUri:="<NamespaceUri.Value of binary Dictionary-Node>")>
Friend Class MyUnionDataType
    Implements IOpcUnion

    '0 = no field, 1 = 'Number', 2 = 'Text'.
    Public Property SwitchValue As UInteger Implements IOpcUnion.SwitchValue

    Public Property Number As Integer

    Public Property Text As String
End Class
```

## Datentypen mit optionalen Feldern

Beteiligte Typen: `OpcClient`, `OpcNodeId`, `OpcData`, `OpcDataTypeAttribute`, `OpcDataTypeEncodingAttribute`, `OpcDataTypeEncodingMaskAttribute`, `OpcEncodingMaskKind`, `OpcDataTypeMemberSwitchAttribute`, `OpcMemberSwitchOperator`

Um die übertragene Datenmenge zu reduzieren, können Sie bestimmte Felder eines strukturierten Datentyps im Datenstrom als vorhanden oder fehlend kennzeichnen. Das hängt von bestimmten Bedingungen oder vom Wert eines anderen Felds in der Datenstruktur ab. Ob ein Feld vorhanden ist, zeigt also entweder der Wert eines anderen Felds oder ein einzelnes Bit in der `EncodingMask`. Die `EncodingMask` ist ein Feld, das als Präambel vor den Daten im Datenstrom kodiert ist. Ihre Größe in Bytes geben Sie im `OpcDataTypeEncodingMaskAttribute` an. Wenn Sie die Eigenschaft `Size` nicht explizit festlegen, erhält sie bei `OpcEncodingMaskKind.Auto` die kleinste nötige Anzahl Bytes, berechnet aus der Anzahl der optionalen Felder.

Optionale Felder, deren Vorhandensein ein Bit der `EncodingMask` anzeigt, definieren Sie bei `OpcEncodingMaskKind.Auto` auf drei Arten. Sie verwenden Nullable-Typen (`Nullable`), das `OpcDataTypeMemberSwitchAttribute` ohne Parameter (das nächste freie Bit) oder das `OpcDataTypeMemberSwitchAttribute` mit der Nummer eines bestimmten Bits:

```csharp
[OpcDataType("<NodeId of DataType Node>")]
[OpcDataTypeEncoding(
        "<NodeId of Binary Encoding Node>",
        NamespaceUri = "<NamespaceUri.Value of binary Dictionary-Node>")]
[OpcDataTypeEncodingMask(OpcEncodingMaskKind.Auto)]
internal struct MyDataTypeWithOptionalFields
{
    public short FieldA;
    public int FieldB;
    public string FieldC;
 
    // Nullables are treat as optional fields by default.
    // Existence-Indicator-Bit is Bit0 in the encoding mask.
    public uint? OptionalField1;
 
    // Existence-Indicator-Bit is Bit1 (the next unused bit) in the encoding mask.
    [OpcDataTypeMemberSwitch]
    public int OptionalField2;
 
    // Existence-Indicator-Bit is Bit3 (bit 2 is unused) in the encoding mask.
    [OpcDataTypeMemberSwitch(bit: 3)]
    public byte OptionalField3;
}
```

```vbnet
<OpcDataType("<NodeId of DataType Node>")>
<OpcDataTypeEncoding( _
        "<NodeId of Binary Encoding Node>", _
        NamespaceUri:="<NamespaceUri.Value of binary Dictionary-Node>")>
<OpcDataTypeEncodingMask(OpcEncodingMaskKind.Auto)>
Friend Structure MyDataTypeWithOptionalFields
    Public FieldA As Short
    Public FieldB As Integer
    Public FieldC As String

    'Nullables are treat as optional fields by default.
    'Existence-Indicator-Bit is Bit0 in the encoding mask.
    Public OptionalField1 As UInteger?

    'Existence-Indicator-Bit is Bit1 (the next unused bit) in the encoding mask.
    <OpcDataTypeMemberSwitch>
    Public OptionalField2 As Integer

    'Existence-Indicator-Bit is Bit3 (bit 2 is unused) in the encoding mask.
    <OpcDataTypeMemberSwitch(3)>
    Public OptionalField3 As Byte
End Structure

```

Wenn das Vorhandensein eines Felds stattdessen vom Wert eines vorangehenden Felds abhängen soll, geben Sie beim `OpcDataTypeMemberSwitchAttribute` den Namen dieses Felds an, optional zusammen mit einem Vergleichswert und einem `OpcMemberSwitchOperator`. Ein solches Feld belegt kein Bit der `EncodingMask`:

```csharp
[OpcDataType("<NodeId of DataType Node>")]
[OpcDataTypeEncoding(
        "<NodeId of Binary Encoding Node>",
        NamespaceUri = "<NamespaceUri.Value of binary Dictionary-Node>")]
internal struct MyDataTypeWithConditionalFields
{
    public bool FieldD;
 
    // 'OptionalField4' exists only if the value of 'FieldD' is equals 'true'.
    [OpcDataTypeMemberSwitch("FieldD")]
    public string OptionalField4;
 
    public int FieldE;
 
    // 'OptionalField5' exists only if the value of 'FieldE' is greater than '42'.
    [OpcDataTypeMemberSwitch("FieldE", value: 42, @operator: OpcMemberSwitchOperator.GreaterThan)]
    public string OptionalField5;
}
```

```vbnet
<OpcDataType("<NodeId of DataType Node>")>
<OpcDataTypeEncoding( _
        "<NodeId of Binary Encoding Node>", _
        NamespaceUri:="<NamespaceUri.Value of binary Dictionary-Node>")>
Friend Structure MyDataTypeWithConditionalFields
    Public FieldD As Boolean

    ''OptionalField4' exists only if the value of 'FieldD' is equals 'true'.
    <OpcDataTypeMemberSwitch("FieldD")>
    Public OptionalField4 As String

    Public FieldE As Integer

    ''OptionalField5' exists only if the value of 'FieldE' is greater than '42'.
    <OpcDataTypeMemberSwitch("FieldE", 42, OpcMemberSwitchOperator.GreaterThan)>
    Public OptionalField5 As String
End Structure

```

In OPC UA beschreibt eine DataTypeDefinition optionale Felder nur über Bits der `EncodingMask`. Andere Clients, die einen solchen Datentyp nur über seine DataTypeDefinition kennen, können ihn deshalb unter Umständen nicht dekodieren.
