Kā jau mēs visi zinām, labā prakse ir visādas konstantes, nemainīgas vērtības un citu lietderīgu informāciju sistēmās definēt vienreiz un izmantot atkārtoti. Tomēr daudzās vietās mēs redzam apmēram šādu kodu:
/// <summary>
/// Service for policy related operations.
/// </summary>
[ServiceBehavior(Namespace = “http://service-provider.com/project/service/”)]
public class PolicyService : IPolicyService
{
vai arī teiksim kaut kādu aptuveni šādu koda fragmentu:
if (customer.Age > 78)
{
RaiseError("MaximumCustomerAge");
}
Trūkums šādai konstantes “iešūšanai” kodā ir izmaiņu process. Nekad nevar zināt, kurās vietās un kādos nosacījumos tiek pielietotas šīs maģiskās vērtības. Šādu vērtība nomaiņa dažreiz prasa full-text search, ir nestabila un negarantē viennozīmīgus rezultātus – ka visās vietās, kodā maģiskā vērtība ir nomainīta sekmīgi.
Šīs problēmas risinājums ir izmantot konstantes. Galvenais ieguvums šādai pieejai, protams, ir izmaiņu lokalizācija un sava veida garantija, ka veicot izmaiņas tikai konstantes vērtībā, tā tiek ņemta vērā visās sistēmas sadaļās, kur tā tiek pielietota.
Bet… šeit ir viens catch…
.Net platformā eksistē divu veidu konstantes:
- Kompilācijas laika konstantes.
- Runtime laika konstantes.
Kompilācijas laika konstantes tiek definētas ar keyword const un tiek izmantotas kompilācijas laikā. Savukārt, runtime konstantes tiek definētas ar keyword readonly, tās tiek inicializētas klases konstruktora laikā un tās nav iespējams mainīt pēc klases uzkonstruēšanas jeb instances izveidošanas.
Galvenais catch konstantes pielietošanā ir kompilācijas laika konstantes nepareiza pielietošana. Kompilācijas laika konstantes vērtība performances nolūkos tiek ievietota izsaukuma vietā. Savukārt runtime laika konstantes tiek inicializētas klases konstruktora laikā un vietas, kurās tā tiek izmantota, uztur referenci uz šo vērtību.
Piemēram, augstāk redzamo koda fragmentu, kas pārbauda klienta vecumu aizvietosim ar kompilācijas laika konstanti:
private const int MaxAge = 78;
private static void Main(string[] args)
{
var customer = new Customer { Age = 55 };
if (customer.Age > MaxAge)
{
RaiseError("MaximumCustomerAge");
}
}
Aplūkojot kompilēto kodu redzams, ka if() pārbaudē nav vairs reference uz MaxAge mainīgo, bet ir jau iekompilēta konkrēta vērtība – 78.
IL:
Dekompilēts C#:
if (customer.Age > 0x4e)
{
RaiseError("MaximumCustomerAge");
}
Savukārt pārveidojot kompilācijas laika konstanti par runtime laika konstanti:
private static const readonly int MaxAge = 78;
private static void Main(string[] args)
{
var customer = new Customer { Age = 55 };
if (customer.Age > MaxAge)
{
RaiseError("MaximumCustomerAge");
}
}
Redzams, ka kompilators liek referenci uz mainīgo nevis tā vērtību:
Kāds tad varētu būt potenciālais risks kompilācijas laika konstantes pielietojumos?
Patiesībā interesantākās problēmas sākas software patch un citu ielāpu deployment un smoke testing laikā, kad atklājas, ka vecās bibliotēkas, kuras tomēr izmantoja konstantes no moduļa, kas atjaunots kopā ar patch, nez kāpēc izmanto vēl vecās konstantes vērtības un par nelaimi vēl piedalās kaut kādos matemātiskos vai finansiālos aprēķinos.
Iemesls kāpēc varētu vajadzēt izmantot kompilācijas laika konstantes nevis runtime laika konstantes – ir to pielietojums atribūtu izmantošanā. Diemžēl jeb par laimi atribūti var saturēt tikai kompilācijas laikā zināmas konstantes, kā atribūtu īpašību vērtības.
Nav iespējams dekorēt target site ar atribūtu, kurš definēts izmantojot runtime laika konstanti (notiks kompilatora kļūda – “An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type”):
[CustomAttribute(ThisIsMagicString)]
internal class Program
{
private static readonly string ThisIsMagicString = "magic-string";
Pēdējā laikā secinājām, ka konstantes ĻOTI ērti ir definēt WCF servera servisa pusē, regulējot visu servisu root vārdu telpu:
/// <summary>
/// Service for policy related operations.
/// </summary>
[ServiceBehavior(Namespace = Constants.RootNamespace + “/PolicyService”)]
public class PolicyService : IPolicyService
{
Abiem konstantes veidiem ir savi pielietojumi, tikai jāzina side-effekti katram no tiem.
Cerams, ka noderēs!
Happy coding…