Es gibt in Microsoft .NET alle gebräuchlichen Möglichkeiten, Zahlen zu runden:
using System;
namespace RoundTest
{
class Program
{
static void TestOutput(double value)
{
Console.WriteLine();
Console.Write(value);
Console.Write("\t Truncate: " + Convert.ToInt32(Math.Truncate(value)));
Console.Write("\t Ceiling : " + Convert.ToInt32(Math.Ceiling(value)));
Console.Write("\t RoundAFZ: " + Convert.ToInt32(Math.Round(value, MidpointRounding.AwayFromZero)));
Console.Write("\t RoundToE: " + Convert.ToInt32(Math.Round(value, MidpointRounding.ToEven)));
}
static void Main(string[] args)
{
TestOutput(10.0);
// ...
TestOutput(-10.51);
Console.ReadLine();
}
}
} |
using System;
namespace RoundTest
{
class Program
{
static void TestOutput(double value)
{
Console.WriteLine();
Console.Write(value);
Console.Write("\t Truncate: " + Convert.ToInt32(Math.Truncate(value)));
Console.Write("\t Ceiling : " + Convert.ToInt32(Math.Ceiling(value)));
Console.Write("\t RoundAFZ: " + Convert.ToInt32(Math.Round(value, MidpointRounding.AwayFromZero)));
Console.Write("\t RoundToE: " + Convert.ToInt32(Math.Round(value, MidpointRounding.ToEven)));
}
static void Main(string[] args)
{
TestOutput(10.0);
// ...
TestOutput(-10.51);
Console.ReadLine();
}
}
}
Hier ist die Ergebnisausgabe davon zu finden:
10 Truncate: 10 Ceiling : 10 RoundAFZ: 10 RoundToE: 10
10,49 Truncate: 10 Ceiling : 11 RoundAFZ: 10 RoundToE: 10
10,5 Truncate: 10 Ceiling : 11 RoundAFZ: 11 RoundToE: 10
10,51 Truncate: 10 Ceiling : 11 RoundAFZ: 11 RoundToE: 11
-10 Truncate: -10 Ceiling : -10 RoundAFZ: -10 RoundToE: -10
-10,49 Truncate: -10 Ceiling : -10 RoundAFZ: -10 RoundToE: -10
-10,5 Truncate: -10 Ceiling : -10 RoundAFZ: -11 RoundToE: -10
-10,51 Truncate: -10 Ceiling : -10 RoundAFZ: -11 RoundToE: -11 |
10 Truncate: 10 Ceiling : 10 RoundAFZ: 10 RoundToE: 10
10,49 Truncate: 10 Ceiling : 11 RoundAFZ: 10 RoundToE: 10
10,5 Truncate: 10 Ceiling : 11 RoundAFZ: 11 RoundToE: 10
10,51 Truncate: 10 Ceiling : 11 RoundAFZ: 11 RoundToE: 11
-10 Truncate: -10 Ceiling : -10 RoundAFZ: -10 RoundToE: -10
-10,49 Truncate: -10 Ceiling : -10 RoundAFZ: -10 RoundToE: -10
-10,5 Truncate: -10 Ceiling : -10 RoundAFZ: -11 RoundToE: -10
-10,51 Truncate: -10 Ceiling : -10 RoundAFZ: -11 RoundToE: -11
Möchte man Extensions in C# verwenden, scheint man auf das 3er Net-Framework angewiesen zu sein. Dies ist aber nicht ganz richtig. Legt man folgende Klasse in dem Projekt an:
namespace System.Runtime.CompilerServices
{
public class ExtensionAttribute : Attribute {}
} |
namespace System.Runtime.CompilerServices
{
public class ExtensionAttribute : Attribute {}
}
so kann man Extensions, eine 3er Compiler vorrausgesetzt (VS2008), problemlos mit dem .NET Framework 2 als Ziel verwenden.
Bereits mit der Version 2.0 wurde C# um ein mächtiges Feature erweitert: yield return.
Mit C# 3.0 kam dann unter anderem das var dazu. Dazu ein kleines Beispiel:
private static void TestList()
{
var list = new List<String> {
"Dies ist Zeile 1",
"HIER KOMMT ZEILE 2",
"3 hat nichts",
"4 IST NOCH MAL EIN TREFFER" };
foreach (var item in list)
{
Console.WriteLine(item);
}
Console.WriteLine();
foreach (var item in FilterUpperLines(list))
{
Console.WriteLine(item);
}
}
private static IEnumerable<String> FilterUpperLines(List<String> list)
{
foreach (string elem in list)
{
if (elem.ToUpper() == elem)
yield return elem;
}
} |
private static void TestList()
{
var list = new List<String> {
"Dies ist Zeile 1",
"HIER KOMMT ZEILE 2",
"3 hat nichts",
"4 IST NOCH MAL EIN TREFFER" };
foreach (var item in list)
{
Console.WriteLine(item);
}
Console.WriteLine();
foreach (var item in FilterUpperLines(list))
{
Console.WriteLine(item);
}
}
private static IEnumerable<String> FilterUpperLines(List<String> list)
{
foreach (string elem in list)
{
if (elem.ToUpper() == elem)
yield return elem;
}
}
var kann bei einer Variablendeklaration anstelle des Typs gesetzt werden, sofern die Definition für den Compiler zwingend ist. Das macht meiner Meinung nach immer dann Sinn, wenn der Typ beispielsweise durch die Initialisierung direkt dahinter nochmals wiederholt wird.
yield kann einem sehr viel Arbeit abnehmen, wenn man Mengen zurückgeben möchte. yield return sammelt quasi die Ergebnismenge ein und gibt sie in Form einer Liste zurück. Das eigentliche Definieren und zurückgeben der Liste übernimmt komplett der Compiler.
Der Clou am var ist übrigens: Diese Funktionen lassen sich auch bei einem Compilat für das Net-Framework 2.0 nutzen! Der Compiler braucht für die Funktionalität keine Features aus dem 3er Framework.
Seit dem Wechsel von Delphi auf C# habe die sprechende Art, mit Aufzählungen zu arbeiten, vermisst:
type
Right = (rRead, rWrite, rDelete);
Rights = set of Right;
var rightSet: Rights;
begin
rightSet := [rRead, rWrite];
Writeln('Schreibrecht: ' + BoolToStr(rWrite in rightSet));
rightSet := rightSet - [rWrite];
Writeln('Schreibrecht: ' + BoolToStr(rWrite in rightSet));
Readln;
end. |
type
Right = (rRead, rWrite, rDelete);
Rights = set of Right;
var rightSet: Rights;
begin
rightSet := [rRead, rWrite];
Writeln('Schreibrecht: ' + BoolToStr(rWrite in rightSet));
rightSet := rightSet - [rWrite];
Writeln('Schreibrecht: ' + BoolToStr(rWrite in rightSet));
Readln;
end.
Das gleiche in C# war durchaus effektiv und klar, aber die boolschen Operationen lassen sich oft nicht so intuitiv lesen bzw. umsetzen:
[Flags]
enum Rights
{
Read = 1,
Write = 2,
Delete = 4,
}
private static void BoolscherCode()
{
// üblicher Code
Rights rights = Rights.Read | Rights.Write;
// prüfen, ob Schreiben enthalten ist:
Console.WriteLine("Schreibrecht: " + ((rights & Rights.Write) == Rights.Write).ToString());
// Schreibrecht entfernen
rights &= ~Rights.Write;
// prüfen, ob Schreibrecht enthalten ist:
Console.WriteLine("Schreibrecht: " + ((rights & Rights.Write) == Rights.Write).ToString());
} |
[Flags]
enum Rights
{
Read = 1,
Write = 2,
Delete = 4,
}
private static void BoolscherCode()
{
// üblicher Code
Rights rights = Rights.Read | Rights.Write;
// prüfen, ob Schreiben enthalten ist:
Console.WriteLine("Schreibrecht: " + ((rights & Rights.Write) == Rights.Write).ToString());
// Schreibrecht entfernen
rights &= ~Rights.Write;
// prüfen, ob Schreibrecht enthalten ist:
Console.WriteLine("Schreibrecht: " + ((rights & Rights.Write) == Rights.Write).ToString());
}
Wintellect bietet mit seiner PowerCollection hier durchaus einen Ausweg:
enum Rights { Read, Write, Delete }
private static void ListenCode()
{
Wintellect.PowerCollections.Set rights = new Wintellect.PowerCollections.Set();
rights.Add(Rights.Read);
rights.Add(Rights.Write);
// prüfen, ob Schreiben enthalten ist:
Console.WriteLine("Schreibrecht: " + (rights.Contains(Rights.Write)).ToString());
// Schreibrecht entfernen
rights.Remove(Rights.Write);
// prüfen, ob Schreibrecht enthalten ist:
Console.WriteLine("Schreibrecht: " + (rights.Contains(Rights.Write)).ToString());
} |
enum Rights { Read, Write, Delete }
private static void ListenCode()
{
Wintellect.PowerCollections.Set rights = new Wintellect.PowerCollections.Set();
rights.Add(Rights.Read);
rights.Add(Rights.Write);
// prüfen, ob Schreiben enthalten ist:
Console.WriteLine("Schreibrecht: " + (rights.Contains(Rights.Write)).ToString());
// Schreibrecht entfernen
rights.Remove(Rights.Write);
// prüfen, ob Schreibrecht enthalten ist:
Console.WriteLine("Schreibrecht: " + (rights.Contains(Rights.Write)).ToString());
}
ConnectionStrings werden von Visual Studio automatisch in die Anwendungseinstellungen übernommen und beinhalten bei lokalen Datenbanken (beispielsweise VistaDB) üblicherweise auch den absoluten Pfad. Für Asp.Net bietet sich wohl eine solche Lösung an:
Data Source=|DataDirectory|\MyDatabase.vdb3
Das funktioniert aber nicht bei WinForm-Anwendungen, weil es das DataDirectory dort nicht gibt. In dem Falle kann man den ConnectionString aber für die Entwicklungsumgebung fix setzen und dann einfach zur Laufzeit korrigieren:
string connString = global::MyLib.Properties.Settings.Default.ConnectionStringMyApp;
string dbName = "MyDatabase.vdb3";
Int32 startPos = connString.IndexOf("Data Source =\"") + 13;
Int32 endPos = connString.IndexOf(dbName + "\"") + dbName.Length;
connString = connString.Substring(0, startPos + 1) +
"D:\\MyDataDir\\" + dbName + connString.Substring(endPos);
global::MyLib.Properties.Settings.Default["ConnectionStringMyApp"] = connString; |
string connString = global::MyLib.Properties.Settings.Default.ConnectionStringMyApp;
string dbName = "MyDatabase.vdb3";
Int32 startPos = connString.IndexOf("Data Source =\"") + 13;
Int32 endPos = connString.IndexOf(dbName + "\"") + dbName.Length;
connString = connString.Substring(0, startPos + 1) +
"D:\\MyDataDir\\" + dbName + connString.Substring(endPos);
global::MyLib.Properties.Settings.Default["ConnectionStringMyApp"] = connString;
Das funktioniert, wenn man weiß, dass die Properties in den Settings problemlos über Default[] beschrieben werden können.