Labvakar! Labrīt!
Šoreiz vēlos pastāstīt par .NET platformas nostūri, kurš reizēm tiek nepamatoti piemirsts un nesaprasts - formatējumu realizācijas un kultūras informācija. Domāju, ka neesmu vienīgais, kuram ir gadījies sastapties ar koda analīzes nosacījumu, kurš pieprasa norādīt formatēšanas realizācijas interfeisu (IFormatProvider). Droši vien arī ne vienīgais, kuram šis nosacījums ir izraisījis izbrīnu.
Pēc tam, kad pāris mēnešus atpakaļ izlasīju Džefa Atvuda rakstu par programmatūras internacionalizācijas problēmām, šis nosacījums kļuva nedaudz skaidrāks, bet patiesi es sapratu pēc tam, kad pāris reizes uzdūros situācijām, kad šī nosacījuma neievērošana izsauca mistiskas problēmas.
Lai ilustrētu situāciju apskatīsim šādu, vienkāršu programmu:
using System;
using System.Data;
namespace CultureDemo
{
class Program
{
static void Main()
{
DataTable table = new DataTable("testTable");
table.Columns.Add("value", typeof (decimal));
Console.WriteLine("+-------+");
Console.WriteLine("| Value\t|");
Console.WriteLine("+-------+");
Random r = new Random(DateTime.Now.Millisecond);
for (int i = 0; i < 10; i++)
{
DataRow row = table.NewRow();
row["value"] = (decimal)r.Next(1000) / 100;
table.Rows.Add(row);
Console.WriteLine("| {0:f2}\t|", row["value"]);
}
Console.WriteLine("+-------+");
Console.WriteLine();
decimal treshold = (decimal) r.Next(1000) / 100;
int rowCount = table.Select(String.Format("value < {0}", treshold)).Length;
Console.WriteLine("{0} rows under treshold {1}", rowCount, treshold);
}
}
}
Programma nedara neko sarežģītu - izveido datu tabulu, aizpilda ar 10 patvaļīgiem decimāldaļskaitļiem, izvēlas kādu patvaļīgu robežpunktu un saskaita cik ieraksti ir ar vērtību, kas mazāka par robežpunktu. Tomēr pat tik triviāla programma "izgāžas" ar eksepciju, ja to darbina uz datora, kuram reģionālajos iestatījumos ir uzstādītas Latvijai raksturīgās vērtības.
Papētot sīkāk izrādās, ka problēma ir rindā, kurā ģenerē atlases kritērijus, konkrētāk tajā apstāklī, ka Latvijā kā decimālais atdalītājs tiek lietots komats (un tas pēc noklusējuma tiek izmantots String.Format un citās, līdzīgās metodēs), bet .NET rindu filtrā sagaida, ka decimālais atdalītājs būs punkts. Šādos gadījumos palīgā nāk invariantā kultūra CultureInfo.InvariantCulture. Tātad, izmainītais kods:
using System;
using System.Data;
using System.Globalization;
namespace CultureDemo
{
class Program
{
static void Main()
{
DataTable table = new DataTable("testTable");
table.Columns.Add("value", typeof (decimal));
Console.WriteLine("+-------+");
Console.WriteLine("| Value\t|");
Console.WriteLine("+-------+");
Random r = new Random(DateTime.Now.Millisecond);
for (int i = 0; i < 10; i++)
{
DataRow row = table.NewRow();
row["value"] = (decimal)r.Next(1000) / 100;
table.Rows.Add(row);
Console.WriteLine("| {0:f2}\t|", row["value"]);
}
Console.WriteLine("+-------+");
Console.WriteLine();
decimal treshold = (decimal) r.Next(1000) / 100;
int rowCount = table.Select(String.Format(CultureInfo.InvariantCulture, "value < {0}", treshold)).Length;
Console.WriteLine("{0} rows under treshold {1}", rowCount, treshold);
}
}
}
Un rezultāts:
Līdzīgas problēmas (un risinājums) ir gadījumos, kad jāveic atlase pēc datuma. Tad izmantojam šo pašu recepti, lai izvairītos no problēmām.
Ja ar laiku pierod norādīt formatēšanas realizāciju izsaucot dažādas metodes datu noformēšanā, tad var nonākt otrā galējībā, t.i., visur sākt norādīt invarianto kultūru. Jāatzīmē, ka tas nebūs īsti korekti, jo pareizi būtu lietotājam informāciju attēlot tādā formātā, kāda tas ir pieradis. Tāpēc gadījumos, kad datus formē parādīšanai lietotājam, pareizi būtu izmantot System.Threading.Thread.CurrentThread.CurrentUICulture, kā formatēšanas realizāciju.
Nolēmu atjaunot zemsvītras piezīmes. Šajās varu pastāstīt, ka ir izdevies ciest no saulītes ar zobiem un tagad sēžu ar sūrstošu muguru :)