3 .NET
3.1 Versionen
Version
|
Erscheinungsjahr
|
Anmerkung
|
0.?
|
2000
|
Vorstellung des .NET Konzeptes auf der
Entwicklerkonferenz PDC
|
1.0
|
2002
|
Auslieferung der Version 1.0 mit zugehörigen
SDK
|
1.1
|
2003
|
Geringfügig verbessertes SDK 1.0 + Visual
Studio 2003
|
2.0
|
2005
|
Vollständig
überarbeitetes Framework +m Visual Studio 2005
generische
Klassen
partielle
Klassen
In großen
Teilen neu impelmentiertes ASP.NET
stark
verbessertes Intellisense in VS 2005
viele neue Designer in VS2005
|
3.0
|
2006
|
Erweiterungen
um Klassenbibliotheken speziell für Windows Vista:
Windows
Communication Foundation (WCF)
Windows
Presentation Foundation (WPF)
Windows
Workflow Foundation (WF)
Windows
CardSpace (WCS)
|
3.5
|
2008
|
Baut
auf .NET 2.0 auf und fügt im wesentlichen hinzu:
Visual Studio
2008, kann jetzt auch Projekte für .NET 2.0 und .NET 3.5
verwalten
|
4.0
|
2010
|
Neue CLR und neuer Global Assembly Caches
(GAC) nun C:\Windows\Microsoft.NET\assembly.
Client
Profile: abgespeckte Version der CLR, die auf einer größeren
Anzahl von Plattformen bereitgestellt werden kann
Paralleler
Betrieb von CLR's verschiedener Versionen im gleichen Prozess
GC läuft
jetzt im Hintergrund
Codeverträge
(System.Diagnostics.Contracts): Definition von Vor-,
Nechbedingungen und Objektinvarianzen von Methodenaufrufen
möglich
Erweiterung
der CLR um die DLR zur Implementierung dynamischer Sprachen
Klassenbibliotheken
für BigInteger, komplexe Zahlen und Tupel
verzögerte
Instanziierung mittels System.Lazy<T>
Integration
des Observer- Designpatterns mit System.IObservable<T>
Tasks- neues Programmiermodell für
parallel- Computing
|
4.5
|
2012
|
Neue CLR. Jedoch MSIL kompatibel mit 4.0->
4.5 exe läuft auch unter 4.0, falls keine neuen Klassen
eingesetzt werden.
Baut auf 4.0 auf (inplace- Installation).
Neuerungen:
Apps für
Windows AppStore entwicklen
Portable
Klassenbibliotheken zur Plattformübergreifenden
Programmierung auf Windows Phone und PC
Arrays >
2GB auf 64bit Plattformen werden unterstützt
JITCompiler
und GC laufen jetzt im Hintergrund (asynchron)
UniCode- IO
auf der Kommandozeile (Console.WriteLine) jetzt möglich !
ASP.NET 4.5
Windows Server 2003 und Windows XP werden
nicht mehr unterstützt !
|
3.2 Onlinehilfe
Zum SDK wird eine umfangreiche Onlinehilfe
mitgeliefert. Sie ist die primäre Dokumentation zum .NET
Framework.
3.2.1 Zum angezeigten Thema den zugehörigen
Eintrag im Inhaltsverzeichnis finden
Zu jeder Hilfetafel kann der zugehörige
Eintrag im Inhaltsverzeichnis aufgeklappt werden über die
Funktion Mit Inhaltsverzeichnis synchronisieren welche über
den Symbolleistenlink
gestartet wird. So können über Index oder
Suchen gefundene Tafeln schnell in den gesamten Kontext
des Dokumentes eingeordnet werden.
3.3 Das .NET
Das .NET ist im Wesentlichen eine Spezifikation
für ein Framework zur Programmierung in einer vernetzten Welt.
Durch Implementierungen von .NET werden Programme plattformneutral
(unabhängig von der CPU und dem Betriebssystem),
resourcenschonend und kontrollierbar während der Ausführung
(Sicherheit). Die Spezifikation gliedert sich in folgende Teile:
|
|
CLR
|
Common Language Runtime: Definiert eine
eine einheitliche Laufzeitumgebung für .NET Programme. Diese
besteht aus:
Einem Just In Time Compiler zur Übersetzung der MSIL
(Microsoft Intermediate Language) in die sog. systemeigene
Maschinensprache (z.B. x86 Maschinensprache)
Einer automatischen Heapspeicherverwaltung, genannt GC (Garbage
Collector)
Einer Prüfung beim Ladevorgang, der die Typsicherheit des
Codes prüft
Einem System zur Gewährleistung der Zugriffssicherheit zur
Laufzeit (Code Access Security)
Konfiguration einer Anwendung mittels XML- Dateien
|
CLS
|
Common Language Specification:
Definiert ein Subset von Merkmalen, die alle Programmiersprachen
im .NET erfüllen müssen. Sprachen wie C# oder C++
besitzen über die CLS hinausgehende Sprachfeatures wie unit
(C#) oder Referenztypen auf dem Stack (C++). Um Interoperabilität
zwischen den Sprachen zu gewährleisten, sollten CLS-
konforme Passagen im Programm mittels des Attributes
CLSCompliantAttribute markiert werden. Der C# Compiler
überprüft die markierten Passagen und gibt im Falle von
nicht konformen Code zur Übersetzungszeit einen Fehler aus
(C++ hat diese Komfortfunktion nicht!)
|
CTS
|
Common Type Specification: Definiert
elementare Datentypen auf der Bitebene als Grundlage der
eingebauten Datentypen für alle .NET Programmiersprachen.
Festkommatypen werden dabei nach little Endian (niederwertige
Bytes auf den kleineren Adressen, höherwertige Byte auf den
größeren Adressen) dargestellt, und Zeichen oder
Zeichenketten durch UTF16 kodiert.
|
3.3.1 Schichtenmodell
3.4 Windows RT, Apps und Appstore (ab 4.5)
Mit Windows 8 wurde eine neue API in der Windows-
Betriebssystemfamilie eingeführt: die Windows Runtime oder
kurz WinRT. WinRT ist
ein objektorientierte Betriebssystem- API. Bei ihrer Entwicklung
diente .NET als Blaupause. Viele Klassen aus WinRT ähneln .NET
Klassen. Jedoch ist die WinRT eine in C++ geschriebene COM-
Komponente. Sie ist damit kein Ersatz für das .NET Framework !
3.4.1 Vorteile von WinRT
Geringer Ressourcenverbrauch und hohe
Ausführungsgeschwindigkeit dank optimierter Übersetzung
durch C++ Compiler
Portable API für spezielle
Windowsanwendungen, genannt Apps (z.B. lauffähig auf
Desktop und Windows Phone)
Innovative grafische Benutzeroberfläche
mit Gestensteuerung im "Kacheldesign" (Tiles).
Programmierbar in C++, HTML/Javascript und
mittels spezieller CLR auch in C#/VB.NET und XAML
3.4.2 Nachteile von WinRT
Läuft erst ab Windows 8- Windows 7,
Vista, XP werden nicht unterstützt !
Kein Garbage Collector. Stattdessen
Speicherverwaltung durch Referenzzählung wie in COM üblich.
Apps haben aus Sicherheitsgründen
ähnlich wie Webanwendungen im Browser einen auf Minimum
beschränkten Zugriff auf lokale Ressourcen (z.B. Dateisystem,
Netzwerk). Echte Desktop Anwendungen sind nicht ohne tiefgreifende
Änderungen in der Architektur in Apps umprogrammierbar.
Apps können nur von einem speziellen
Server, dem Appstore geladen werden.
Browser basierte Webanwendungen und Apps
haben das gleiche Einsatzgebiet (plattformneutraler Zugriff auf
Inhalte im Netz). Jedoch sind die Browser basierten Anwendungen
wesentlich portabler. So ist eine Browser basierte Anwendung auf
jedem Gerät ausführbar, das einen Browser anbietet –
das sind fast alle (PC's, Mobilgeräte). Apps sind nur auf
Windows 8 und Windows Phone ausführbar. Auch stehen die
Browser basierten Anwendungen in ihrer funktionalen Möglichkeiten
dank HTML5 Erweiterungen wie Canvas und local Storage den Apps in
keiner Weise nach.
3.4.3 Fazit
Wer dem Zeitgeist dienen muss innerhalb der
Windows- Plattform, kommt an Apps und WinRT nicht vorbei. Ob der
Zeitgeist auch in Zukunft den Ton angeben wird, kann ruhig bezweifelt
werden. Denn WinRT Apps funktionieren nur auf Windows 8/Windows
Phone. Die Masse nutzt derzeit jedoch IPhones mit Mac IOS oder
Android Geräte. Jede dieser alternativen Plattformen hat sein
eigenes, mit den anderen völlig inkompatibles Appframwork. Wer
also alle erreichen möchte, muss für jede Plattform die
inhaltlich gleiche App schreiben. Mit einer Webanwendung erreicht man
hingegen alle. Das ist nicht nur billiger, es sichert auch
langfristige Projekte.
3.5 Applikationsdomänen
Das Windows- Betriebssystem teilt die Ressourcen
wie Arbeitsspeicher, Dateizugriff, und Prozessorzeit über
Prozesse auf Anwendungen auf. Jede laufende unverwaltete Anwendung
wie z.B. Excel belegt dabei mindestens einen Prozess.
Ein Prozess stellt einen virtuellen Adressraum
bereit, der den theoretisch größtmöglichen
Arbeitsspeicher überdeckt. Die Zuordnung der virtuellen Adressen
zu realen erfolgt durch das Betriebssystem. Dabei teilt es den
Adressraum in gleichgroße Teile (Kacheln) auf. Diese können
realen Arbeitsspeicherabschnitten zugeordnet,
auf Festplatte ausgelagert (Swap - Datei)
oder dem Prozess noch nicht zugeordnet sein.
Vorteile der Prozesse:
Mehrere Anwendungen teilen sich den
physischen Arbeitsspeicher (Kacheln verschiedener Prozesse werden
Arbeitsspeicherbereichen zugeordnet)
Anwendungen sind voneinander isoliert
(Einschluss im eigenen virtuellen Adressraum)
Fehlfunktionen einer Anwendung sind auf das
Scheitern des Prozesses beschränkt.
Nachteile von Prozessen:
Wechsel zwischen Anwendungen bewirken
Prozessumschaltungen – ressourcenintensiv
Die CLR ermöglicht eine zusätzliche
Aufteilung eines Prozesses auf mehrere .NET Anwendungen durch
Applikationsdomänen. Der Lader der CLR platziert die
Assemblies der Anwendungen auf disjunkte Adressbereichen innerhalb
eines Prozesses. Die Zugriffe auf den managed Heap werden geprüft.
Der Versuch, auf die Instanzen einer anderen Applikationsdomäne
auf dem Managed Heap zuzugreifen, wird von der CLR unterbunden.
Hierdurch können die Nachteile von Prozessen überwunden
werden. Das wirkt sich insbesondere auf Serversystemen aus, die
mehrere Anwendungen (z.B. ASP.NET) für hunderte von Usern
anbieten.
3.6 Assemblies
Übersetzter .NET Code wird in Dateien
gespeichert, die als Assemblies bezeichnet werden. Eine
Assembly besteht aus einem Manifest, Typmetadaten und dem MSIL- Code,
der durch Übersetzen der C# - oder VB.NET Anweisungen entsteht.
Durch die aus den Typmetadaten abgeleitete
Strukturierung des MSIL- Codes kann eine Assembly auch als
objektorientierter Assemblercode betrachtet werden
(http://stackoverflow.com/questions/216841/how-does-mono-work).
Im Manifest werden Name, Version und Kultur der
Assembly definiert. Der Name kann um einen fälschungssicheren
Strong- Name erweitert werden. Dieser entsteht durch Verschlüsseln
des Manifestes einer Assembly mit einem privaten Schlüssel, der
nur dem Entwickler bekannt ist.
3.6.1 Aufbau einer Assembly
3.6.2 Versionsnummer
Aus dem aktuellen Stand seines Programmcodes kann
der Programmierer eine Assembly erstellen. Diese "Momentaufnahme"
bekommt eine eindeutige Nummer in Raum und Zeit, die Versionsnummer.
Die Versionsnummer einer Assembly besteht aus folgenden vier
Teilen, wobei jeder Teil ein Wert aus dem Intervall [0, 65535] sein
darf:
|
Hauptversion
|
Nebenversion
|
Build
|
Revision
|
Beispielwerte
|
1
|
0
|
12
|
1
|
Änderungen in der Assembly, wenn sich
Wert erhöht (++)
|
Neue Assembly mit neuen oder erweiterten
Funktionen, die inkompatibel mit den Vorgängern ist
|
Neue Assembly, die konzeptionell den
Funktionsumfang der Hauptversion hat, gegenüber den
Vorgängern aber besser implementiert. Inkompatibel mit den
Vorgängern
|
Neu erstellte Assembly, die kompatibel mit
allen Vorgängern ist, welche die gleiche Haupt- und
Nebenversion tragen
|
Neu erstellt mit Fehlerkorrekturen
|
Bei Assemblies mit starkem Namen wird beim Binden
an die Anwendung von der Laufzeit geprüft, ob die von der
Anwendung geforderte Versionsnummer mit der Versionsnummer der
Assembly übereinstimmt.
3.6.2.1 Dateiversion vs. Assemblyversion
Für eine Assembly können zwei
Versionsnummer definiert werden:
Assemblyversion
|
An dieser orientiert sich der .NET Lader. Wenn
ein .NET Programm eine Assembly in der Version 3.5.0.0 wünscht,
dann wird genau nach einer solchen mit dieser Assemblyversion
gesucht. Die Assmeblyversion sollte für ein Projekt deshalb
fest definiert- und während des Projektes nicht geändert
werden.
|
Dateiversion
|
Die Dateiversion wird beim Installieren auf dem
Zielsystem berücksichtigt. Wenn während eines Projektes
ständig neue Build's erfolgen, kann hier die Build- Nummer
hochgezählt werden (z.B. 3.5.29.0), damit beim Installieren
auf dem Testsystem tatsächlich eine neue DLL installiert
wird.
|
3.6.3 Global Assembly Cache
Informationen zu allen Assemblys, die von mehreren
Anwendungen gemeinsam benutzt werden können, werden im Global
Assembly Cache (GAC) gespeichert. Dieser kann eingesehen werden über
// bis .NET 3.5
c:\Windows\assembly.
// ab .NET 4.0
C:\Windows\Microsoft.NET\assembly
In diesen Verzeichnissen werden die DLL's in einer Ordnerstruktur mit
folgendem Aufbau gespeichert:
assembly
+-- GAC_32
| :
| +-- System.Data
| : +- v4.0.0.0.0_b77a5c561934e089
| +- System.Data.dll
|
+-- GAC_MSIL
:
+-- System
: +- v4.0.0.0.0_b77a5c561934e089
+- System.dll
Der Zugriffsrechte auf diese
Ordner sind so eingestellt, das alle .NET Anwendungen auf dem System
diese Assemblies laden können.
Die
Installation im GAC kann mittels dem GACUtil.exe Kommandozeilentool,
oder einem Setup- Projekt erfolgen.
3.6.3.1 Sicherung öffentlicher Assemblies mittels
Strong Names
Durch Verschlüsselung von Daten aus dem
Manifest einer Assembly (Name, Version, Kultur, Public Key und
digitale Signatur) mittels eines geheimen privaten Schlüssels
erzeugt der Entwickler einen sogenannten Strong Name. Die
Anwender der Assembly bekommen vom Entwickler einen öffentlichen
Schüssel ausgehändigt, der zum geheimen privaten Schlüssel
passt. Mit dem öffentlichen Schlüssel können die
Anwender die Manifestdaten aus dem Strong Name korrekt entschlüsseln.
Das Verfahren ist so ausgelegt, das Personen, die nicht im Besitz des
geheimen privaten Schlüssels sind, keine Strong Names bilden
können, die sich korrekt mit den öffentlichen Schlüsseln
entschlüsseln lassen.
Programme, die Funktionen einer Strong- Name
Assembly aufrufen, müssen den öffentlichen Schlüssel
zu einem Strong Name kennen. Dieser Schlüssel wird entweder im
globalen Assemblycache beim Installieren der Strong- Name Assembly
hinterlegt, oder in der Konfigurationsdatei mittels
<assemblyIdentity...> und <codeBase...> Elementen
definiert.
Strong
Names haben folgende Eigenschaften:
Strong Names sind eindeutig. Die privaten Schlüssel werden
durch Tools erstellt, die garantieren, daß jeder erzeugte Key
weltweit eindeutig ist, und die mit diesen keys erstellten Namen
ebenfalls eindeutig sind
Strong Names sichern die Integrität einer Assembly:
-
3.6.3.2 Erstellen eines Schlüsselpaares zur
Gewinnung von Strong- Names
Ein Schlüsselpaar kann mittels des
Kommandozeilentools sn wie folgt gewonnen werden:
c:\sn.exe -k MyCompanyKeys.snk
Achtung: Die Datei mit dem
Schlüsselpaar muß sicher aufbewahrt werden.
3.6.3.3 Signieren einer Assembly mit einem Strong Name
Eine Assembly wird vom Entwickler mit einen Strong
Name signiert, indem auf die Schlüsseldatei mittels eines
Attributes in der Datei AssemblyInfo.cs verwiesen wird:
[assembly:AssemblyKeyFileAttribute(@"MyCompanyKeys.snk")]
Der Verweis muss sich dabei relativ auf das Verzeichnis beziehen,
indem die Assembly kompiliert wird. Da Visual Studion die Assemblys
nicht im Projekt, sondern in den Verzeichnissen Debug\bin bzw.
Release\bin kompiliert, muss der Bezug dann folgendermaßen
formuliert werden (wenn die Schlüsseldatei sich im
Projektverzeichnis befindet):
[assembly:AssemblyKeyFileAttribute(@"..\\..\\MyCompanyKeys.snk")]
3.6.3.4 Installation im Global Assembly Cache
Die Installation und Deinstallation kann mit einem
Installationsprogramm oder dem Tool GACUtil.exe erfolgen.
3.6.3.5 Zugriff auf öffentliche Assemblies
3.6.3.6 Merksätze für Assemblies mit starken
Namen
globale Assemblies müssen einen starken
Namen besitzen. Der starke Namen ist die mit einem geheimen
Schlüssel verschlüsselte Checksumme der Assembly.
Assemblies mit starken Namen können nur
auf Assemblies mit starkem Namen verweisen
3.6.4 Festlegen der Speicherorte von privaten
Assemblies, welche eine Anwendung einbindet
Private Assemblies werden gewöhnlich im
Verzeichnis der Anwendung abgelegt. Hat die Verzeichnisstruktur der
Anwendung einen komplexeren Aufbau, dann kann dies in der
Anweundungskonfigurationsdatei beschrieben werden durch das probing
Element.
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<probing privatePath="bin;bin2\subbin;bin3"/>
</assemblyBinding>
</runtime>
</configuration>
Befindet sich eine Anwendung im
Verzeichnis C:\MyApp, dann
werden durch die obige Konfiguration die Assemblies im Verzeichnis
C:\MyApp\bin, C:\MyApp\bin2\subbin und C:\MyApp\bin3 gesucht.
3.7 Just in time Compiler
Auf folgendem strukturierten Zeitstrahl kann die
Wirkungsweise des Just in Time Compilers abgelesen werden.
3.7.1 Optimierungen
3.7.1.1 Rate des Inlinings erhöhen (ab .NET 4.5)
Kleine Unterprogramme/Methoden, die häufig
aufgerufen werden, können einen enormen Overhead durch die
Parameterübergabe verursachen. Deshalb werden beim JIT-
Compilieren Methoden, die z.B. nicht virtuell und kleiner als 32 Byte
MSIL Code sind, nicht aufgerufen sondern an der Aufrufstelle direkt
eingebettet.
Methoden, die die Längenbeschränkung
überschreiten aber dennoch gut Kandidaten für Inlining
sind, annotiert werden mit
System.Runtime.CompilerServices.MethodImpl(MethodImplOptions.AggressiveInlining):
3.8 Werte-, Referenztypen und Garbage Collector
Objekte primitiver Typen wie Integer oder Double
werden in .NET wie in jeder höheren, block- orientierten
Programmiersprache im Stapelspeicher abgelegt. Man spricht von
Wertetypen.
Objekte selbstdefinierter Klassen hingegen müssen
manuell mittels New- Operator im Freispeicher platziert werden, wobei
mit einer im Stapelspeicher befindlichen Referenz- Variable auf diese
verwiesen wird. Man fasst diese auch unter dem Begriff Referenztypen
zusammen.
In klassischen Programmiersprachen muss der
Programmierer mit einem Befehl das Objekt wieder löschen, wenn
es nicht mehr benötigt wird, um so den belegten Platz im
Freispeicher freizugeben und damit wiederverwendbar zu machen. Häufig
jedoch wurde diese Aufgabe unzureichend gelöst, was insbesondere
bei langlaufenden Programmen (z.B. Serveranwendungen) zu einem
"volllaufen" des Freispeichers mit nicht mehr genutzten
Objekten führte. Letztendlich stürzte das Programm dann ab
(Memory- Leak).
In
.NET wird diesem Problem mit dem Garbage Collector begegnet. Dies ist
ein Task der CLR, welcher in nicht vorher bestimmbaren Zeitraster
startet, alle Objekte, die noch über Referenz- Variablen im
Stapel adressiert werden als gültig markiert, um anschließend
alle ungültigen Objekte zu löschen. Hierdurch werden
automatisch alle nicht mehr benötigten Objekte zeitnah aus dem
Freispeicher entfernt und seine Speicherkapazität zu jedem
Zeitpunkt maximiert.
3.9 Object- "Mutter" aller Datentypen
Alle Typen sind auf den Basistyp System.Object
rückführbar. D.h. alle Typen verfügen über
die Eigenschaften und Methoden von System.Object.
3.9.1 Referenztypen vs. Wertetypen
Die
Menge aller Typen, die sich von System.Object ableiten, kann in zwei
Teilmengen aufgeteilt werden: den Referenztypen und den Wertetypen.
Die Wertetypen werden grundsätzlich auf dem Programmstack
angelegt und entsprechen den eingebauten Typen in älteren
Programmiersprachen.
Referenztypen
erfordern eine Instanziierung in 2 Schritten:
3.9.2 Boxing und Unboxing- Konvertierung
Zwecks Erhöhung der
Ausführungsgeschwindigkeit werden Wertetypen auf dem Stack wie
in allen anderen nicht .NET- Sprachen als reine Werte abgelegt. Da
aber alle Typen komplexe Objekte darstellen, erfolgt eine
Konvertierung, wenn ein Wertetyp in einem Objektkontext aufgerufen
wird. Dabei wird temporär auf dem Heap ein Objekt angelegt, und
der Wert des Wertetypen hineinkopiert. Dies wird als boxing
bezeichnet.
// i ist ein Wertetyp
int i = 99;
// obj_1 ist ein Referenztyp und zeigt auf nichts
object obj_1;
// obj_2 ist ein Referenztyp und zeigt auf eine Variable auf dem Stack (?!)
// -> Boxing
object obj_2 = i;
Console.WriteLine("Wert von obj_2= " + obj_2.ToString());
Console.WriteLine("Wert von i= " + i.ToString());
i++;
// Die folgende Ausgaben zeigen, daß obj_2 auf einen anderen Speicherbereich
// zeigt als i.
Console.WriteLine("Wert von obj_2= " + obj_2.ToString());
Console.WriteLine("Wert von i= " + i.ToString());
3.9.3 Spezialwert null
Nicht
initialisierte Referenzen haben immer den Wert
null
.
3.10 Typsicherer Code
Im folgenden wird unter Code ein in die
Maschinensprache übersetztes Programm verstanden.
Ein Code ist Typsicher, wenn er folgenden
Kriterien erfüllt:
Ein Verweis auf einen Typ ist vollständig
kompatibel mit dem Typ, auf den verwiesen wird
Für ein Objekt sind nur die zu seinem
Typ passenden Operationen aufrufbar
Identitäten sind, was sie von sich
behaupten
Bespiel:
using
System;
using System.Collections.Generic;
using System.Text;
namespace u1_TypeSafety
{
class Program
{
// Klasse für Punkte in der Ebene
public class CPoint
{
public int x, y;
}
// Klasse für Linien in der Ebene
public class CLine
{
public CPoint start, end;
}
static void Main(string[] args)
{
try
{
CPoint p1 = new CPoint();
CLine l1 = new CLine();
// Impliziter Cast eines Punktes in ein object
object obj = p1;
// Verbotener Cast, der durch den Compiler entdeckt wird
// CLine l2 = (CLine)p1;
// Verbotener Cast, der durch die CLR entdeckt wird
CLine l2 = (CLine)obj;
}
catch (Exception ex)
{
Console.WriteLine("Fehler: ", ex.Message);
}
}
}
}