Schlagwort: Informatik

Parameter einer C#-Methode per Reflexion ermitteln 👍 👎

In einem früheren Beitrag hatte ich beschrieben, wie sich die Parameter einer Methode in Java programmatisch ermitteln lassen. Zur Reflexion in C# möchte ich gerne einen etwas ausführlicheren Beitrag schreiben; auf Grund mehrfacher Nachfrage möchte ich zwischenzeitlich jedoch zumindest eine ähnliche Funktionalität für C# vorstellen:
Parameter einer C#-Methode per Reflexion ermitteln
0102030405060708
public static IEnumerable<Dictionary<string, string>> GetMethodParameterList(    string typeName,    string methodName) {    return Type.GetType(typeName).GetMethods()        .Where(m => m.Name == methodName)        .Select(m => m.GetParameters().ToDictionary(k => k.Name, v => v.ParameterType.Name));}
Die Verwendung gestaltet sich sehr einfach; wir ermitteln dazu z. B. alle Parameter für Console.WriteLine:
Methode verwenden und Ergebnis auf der Standardausgabe ausgeben
010203040506070809101112
IEnumerable<Dictionary<string, string>> methodParameterList = GetMethodParameterList(    "System.Console",    "WriteLine");
foreach(Dictionary<string, string> method in methodParameterList) { foreach(KeyValuePair<string, string> parameter in method) { Console.WriteLine($"{parameter.Value} {parameter.Key}"); }
Console.WriteLine();}
Falls sich jemand über die verschachtelte Struktur wundert sei darauf hingewiesen, dass es auf Grund der Technik des Überladens mehrere Methoden gleicher Bezeichnung und daher auch mehrere Parameter-Listen geben kann.

C#-Methodenaufruf mit Timeout 👍 👎

Hin und wieder kann es sinnvoll sein, die Ausführung einer Methode zeitlich einzuschränken. Dies trifft besonders häufig dann zu, wenn auf Daten per Netzwerk gewartet werden soll. Eine Implementierung wäre wie folgt denkbar:
Hilfsmethode implementieren
01020304050607080910111213141516
public static class TaskHelper {    public static bool ExecuteWithTimeout(        Action<CancellationToken> action,        TimeSpan timeout,        CancellationToken cancellationToken = default(CancellationToken)    ) {        try {            return Task.Run(() => action(cancellationToken)).Wait(                (int) timeout.TotalMilliseconds,                cancellationToken            );        } catch(OperationCanceledException) {            return false;        }    }}
Die Verwendung gestaltet sich nun sehr einfach für (nahezu) beliebige Aufrufe:
Hilfsmethode verwenden
0102030405060708091011
if(TaskHelper.ExecuteWithTimeout(    cT => Thread.Sleep(TimeSpan.FromSeconds(3)),  // Aufgabe simulieren    TimeSpan.FromSeconds(2)                       // Timeout setzen)) {    /* Ausführung erfolgreich */} else {    /**     * Die Ausführung wurde abgebrochen oder konnte     * nicht in der gegebenen Zeitspanne erfolgen.     */}
In der Praxis bietet sich beispielsweise noch die Unterstützung von Rückgabewerten an. Darüber hinaus sollte die Dokumentation zur CancellationToken-Struktur und allgemein zum Aufgabenabbruch konsultiert werden.

Zertifikat einer Adresse mit C# ermitteln 👍 👎

Manchmal möchte man gerne Zertifikate von Webseiten automatisiert abrufen. Im Rahmen eines Monitorings bietet es sich beispielsweise an, die Gültigkeitsdauer zu überwachen. Dazu eine beispielhafte Implementierung:
Zertifikat einer Webseite ermitteln
010203040506070809
public static X509Certificate2 GetCertificate(string hostName, int port = 443) {    using(TcpClient client = new TcpClient(hostName, port)) {        using(SslStream stream = new SslStream(client.GetStream())) {            stream.AuthenticateAsClient(hostName);
return new X509Certificate2(stream.RemoteCertificate); } }}
Die Implementierung ist dabei – wie üblich – auf das Wesentliche beschränkt; es findet keine umfassende Problembehandlung statt. Sofern die Methode in größerem Kontext zum Einsatz kommen soll, empfiehlt sich auf jeden Fall ein Blick auf die beteiligten Klassen TcpClient, SslStream und X509Certificate2 im MSDN.

Nun stehen uns diverse Informationen zur Verfügung; einige Beispiele zur Ausgabe auf der Konsole:
Methode verwenden und Informationen ausgeben
010203040506
X509Certificate2 certificate = GetCertificate("coders-online.net");
Console.WriteLine(certificate.Subject); // "CN=coders-online.net"
Console.WriteLine(certificate.NotBefore); // 30.04.2017 08:01:00Console.WriteLine(certificate.NotAfter); // 29.07.2017 08:01:00
Im Besonderen sei abschließend noch auf RemoteCertificateValidationCallback hingewiesen, mit welchem sich das Verhalten bei Zertifizierungsfehlern beeinflussen lässt.

Byte-Angaben mit C# formatieren 👍 👎

Mit gewisser Regelmäßigkeit kommt man als Softwareentwickler in die Verlegenheit, Dateigrößen statt in der Form 10110 Byte in der "üblichen" Schreibweise mit größeren Einheiten (in diesem Fall 9.87 KiB) darzustellen.

Zwar gäbe es hier wieder einmal die Möglichkeit per DllImport und beispielsweise StrFormatByteSizeW zu arbeiten, jedoch möchten wir in diesem Fall die Implementierung selbst vornehmen, um die Basis (Stichwort: Binärpräfixe) konsistent bestimmen zu können. Ich stelle dazu zwei mögliche Implementierungen vor:
Hilfsklasse mit Methode zur Formatierung
0102030405060708091011121314151617
public static class Helper {    private static readonly IReadOnlyList<string> unitList = new string[] {        "Byte", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"    };

public static string FormatBytes(double bytes) { int index = 0;
while(bytes >= 1024 && index < Helper.unitList.Count) { bytes /= 1024; index++; }
return $"{Math.Round(bytes, 2).ToString("N2")} {Helper.unitList[index]}"; }}
Alternativ lässt sich das Problem jedoch auch ohne Schleife – und daher mathematisch etwas eleganter – durch ein ähnliches Vorgehen wie bei der Ermittlung der Anzahl der Ziffern einer Zahl mittels Logarithmen lösen:
Alternativer Lösungsansatz
010203040506
public static string FormatBytes(double bytes) {    int index = Convert.ToInt32(Math.Floor(Math.Log(bytes, 1024)));    bytes /= Math.Pow(1024, index);
return $"{Math.Round(bytes, 2).ToString("N2")} {Helper.unitList[index]}";}
Die Verwendung gestaltet sich nun in jedem Fall recht einfach:
Formatierung verwenden
010203
string formattedBytes = Helper.FormatBytes(1024);         // 1,00 KiBformattedBytes = Helper.FormatBytes(1024 * 1024);         // 1,00 MiBformattedBytes = Helper.FormatBytes(1024 * 1024 * 1024);  // 1,00 GiB
Abschließend möchte ich auch noch kurz eine beispielhafte Implementierung zur Verwendung im Rahmen einer benutzerdefinierten Formatierung per String.Format skizzieren:
Implementierung der benutzerdefinierten Formatierung
010203040506070809
public class FileSizeFormatProvider : ICustomFormatter, IFormatProvider {    public string Format(string format, object arg, IFormatProvider formatProvider) {        return Helper.FormatBytes(Convert.ToDouble(arg));    }
public object GetFormat(Type formatType) { return this; }}
Bitte beachtet jedoch, dass diese Implementierung noch nicht vollständig ist. Weiterführende Informationen zur korrekten Implementierung finden sich im MSDN per ICustomFormatter und IFormatProvider. Verwendung:
Verwendung der benutzerdefinierten Formatierung verwenden
01
formattedBytes = String.Format(new FileSizeFormatProvider(), "{0}", (1024 * 1024));  // 1,00 MiB
Ich werde eine vollständige Implementierung vermutlich in einem späteren Beitrag nachreichen.

Zeitserver mit C# abfragen 👍 👎

Im Folgenden möchte ich drei Möglichkeiten vorstellen, wie man mit C# über ein Netzwerk eine Zeitangabe abrufen kann. Es gilt zu beachten, dass es sich um sehr reduzierte Beispiele handelt und beispielsweise keine umfassende Fehlerbehandlung berücksichtigt wird. Wir verwenden standardmäßig jeweils einen Server des NIST.

Time Protocol

Beim Time Protocol handelt es sich um ein sehr einfaches (und etwas betagtes) Protokoll. Der Dienst steht für gewöhnlich unter Port 37 zur Verfügung. Details zum Protokoll können in RFC 868 nachgelesen werden.

Methode zur Zeitabfrage per Time Protocol
010203040506070809101112131415161718
public static DateTime GetTime(string host = "time.nist.gov", int port = 37) {    using(TcpClient client = new TcpClient(host, port)) {        using(NetworkStream networkStream = client.GetStream()) {            // Antwort lesen            byte[] timeBuffer = new byte[4];            networkStream.Read(timeBuffer, 0, timeBuffer.Length);
// Byte-Reihenfolge ggf. umkehren if(BitConverter.IsLittleEndian) { Array.Reverse(timeBuffer); }
return (new DateTime(1900, 1, 1, 0, 0, 0, DateTimeKind.Utc)) .AddSeconds(BitConverter.ToUInt32(timeBuffer, 0)) .ToLocalTime(); } }}

Daytime Protocol

Beim Daytime Protocol verhält es sich ähnlich, ist jedoch textbasiert. Der Dienst steht für gewöhnlich unter Port 13 zur Verfügung. Details zum Rückgabeformat können in RFC 867 und der Dienstbeschreibung des NIST nachgelesen werden – wir werden uns auf den minimalen Part (Datum und Uhrzeit) beschränken.

Methode zur Zeitabfrage per Daytime Protocol
010203040506070809101112131415161718
public static DateTime GetDaytime(string host = "time.nist.gov", int port = 13) {    using(TcpClient client = new TcpClient(host, port)) {        using(StreamReader streamReader = new StreamReader(client.GetStream())) {            // Antwort lesen und auftrennen            string[] response = streamReader.ReadToEnd().Split(' ');
// Datum und Uhrzeit ermitteln int[] date = Array.ConvertAll(response[1].Split('-'), Int32.Parse); int[] time = Array.ConvertAll(response[2].Split(':'), Int32.Parse);
return new DateTime( CultureInfo.CurrentCulture.Calendar.ToFourDigitYear(date[0]), date[1], date[2], time[0], time[1], time[2], DateTimeKind.Utc ).ToLocalTime(); } }}

Network Time Protocol (NTP)

Beim Network Time Protocol handelt es sich um einen weit verbreiteten Standard zur Zeitsynchronisierung in Computernetzwerken über Port 123. Details zum Protokoll können in RFC 5905 nachgelesen werden – wir werden uns wieder auf einen minimalen Teil beschränken. Als Server bietet sich hier natürlich auch die PTB an.

Methode zur Zeitabfrage per NTP
010203040506070809101112131415161718192021222324252627282930313233343536
public static DateTime GetNetworkTime(string host = "time.nist.gov", int port = 123) {    using(UdpClient client = new UdpClient()) {        client.Connect(host, port);
// Anfrage senden const byte header = ( (0 << 6) // LI = 0 (keine Warnung; zur Verdeutlichung ausformuliert) | (4 << 3) // VN = 4 (Version) | (3 << 0) // Mode = 3 (Client) ); // 00_100_011
byte[] request = new byte[48];
request[0] = header;
client.Send(request, request.Length);
// Antwort lesen IPEndPoint endPoint = new IPEndPoint(IPAddress.Any, 0); byte[] response = client.Receive(ref endPoint);
// Daten ab Offset aus ggf. umgekehrter Byte-Reihenfolge ermitteln const int offset = 40;
if(BitConverter.IsLittleEndian) { Array.Reverse(response, offset, 4); Array.Reverse(response, (offset + 4), 4); }
ulong integerPart = BitConverter.ToUInt32(response, offset); ulong fractionalPart = BitConverter.ToUInt32(response, (offset + 4));
return (new DateTime(1900, 1, 1, 0, 0, 0, DateTimeKind.Utc)) .AddMilliseconds((integerPart * 1000) + ((fractionalPart * 1000) / UInt32.MaxValue)) .ToLocalTime(); }}
Insbesondere bei diesem letzten Beispiel möchte ich noch einmal explizit darauf hinweisen, dass es sich um eine sehr reduzierte Implementierung handelt. Die Rückgabe erfolgt bei allen Beispielen jeweils bereits in lokaler Zeit.

Projektverweise

Kategorien / Archiv  |  Übersicht RSS-Feed

Schlagworte

Suche