Back To Future: Paplašinājuma metodes
Turpinot aizsākto tēmu par tuvāko vai tālako nefiltrēto nākotni, kas saistās ar jaunās platformas pielietošanu ikdienas darbā, šoreiz paskatīsim paplašinājuma metodes un un tās nianses, kas varbūt ir palaistas garām citos rakstos.
Ar paplašinājuma metodēm Microsoft izstrādātāju komanda pievienoja jaunu uzvedību .Net platformai īpaši nemainot iekšējos objektus (viens no spilgtākajiem piemēriem ir IEnumerable<T> un IQueryable<T> paplašinājuma metodes, kurās pārsvarā ir realizēta LINQ tehnoloģija). Bet šoreiz nerunāsim par to, cik tās ir labas un smukas, jo tas jau tāpat visiem ir zināms :)
Šoreiz ir udpate šim rakstam. Kā izādās, tad paplašinājuma metodes var pielietot vecajā (2.0) platformā un zemāk ir izstāstīts cik vienkāršā veidā tas ir panākams. Tā kā paplašinājuma metodes neko nemaina eksistējšajā IL kodā un CLR ir palicis nemainīgs. Piemēram šāds kods
internal class Program
{
private static void Main (string[] args)
{
Console.WriteLine(2.Add(2));
Console.WriteLine(IntExt20.Add(2, 2));
Console.ReadLine();
}
}
class IntExt20
{
public static int Add (int x, int y)
{
return x + y;
}
}
static class IntExt35
{
public static int Add (this int x, int y)
{
return x + y;
}
}
IL kodā Main() metode izskatās šāda:
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// Code size 34 (0x22)
.maxstack 8
IL_0000: nop
IL_0001: ldc.i4.2
IL_0002: ldc.i4.2
IL_0003: call int32 ExtensionMethods.IntExt35::Add(int32,
int32)
IL_0008: call void [mscorlib]System.Console::WriteLine(int32)
IL_000d: nop
IL_000e: ldc.i4.2
IL_000f: ldc.i4.2
IL_0010: call int32 ExtensionMethods.IntExt20::Add(int32,
int32)
IL_0015: call void [mscorlib]System.Console::WriteLine(int32)
IL_001a: nop
IL_001b: call string [mscorlib]System.Console::ReadLine()
IL_0020: pop
IL_0021: ret
} // end of method Program::Main
Paplašinājuma metodes izsaukums neatšķiras no parastas statiskas metodes izsaukuma. Vienīgais, kas atšķit paplašinājuma metodi no parastas statiskas metodes ir atribūts:
.method public hidebysig static int32 Add(int32 x,
int32 y) cil managed
{
.custom instance void [System.Core]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( 01 00 00 00 )
// Code size 9 (0x9)
.maxstack 2
.locals init ([0] int32 CS$1$0000)
IL_0000: nop
IL_0001: ldarg.0
IL_0002: ldarg.1
IL_0003: add
IL_0004: stloc.0
IL_0005: br.s IL_0007
IL_0007: ldloc.0
IL_0008: ret
} // end of method IntExt35::Add
Aplūkojot šo atribūtu orģinālājā .Net platformas bibliotēkā ir redzams, ka nekas vairāk par konstruktoru šim atribūtam arī nav.
Tad paplašinājuma metodes sekmīgai darbināšanai vecajā platformā teorētiski būtu nepieciešams tikai šis atribūts, kas atrodas "System.Runtime.CompilerServices" vārdu telpā. To arī nodefinējam:
namespace System.Runtime.CompilerServices
{
[AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public class ExtensionAttribute : Attribute
{
}
}
Pēc programmas pārlikšanas un 2.0 .Net platformu un izpildes -> rezultāts nebija mainījies, kas nozīmē ka programma nostrādāja sekmīgi.
Kā zināms, tad paplašinājuma metodēm ir daudz plusi un padara koda sintaksi smalkāku (varbūt pat brīžiem "confused" nedaudz, jo ir jāpameklē kur atrodas šī paplašinājuma metode un ka tā vispāt ir paplašinājuma metode, ja mēs pārlūkojam kādu eksistējošu kodu jau) tomēr paplašinājuma metodēm ir arī pāris mīnusi:
- paplašinājuma metodes var nodedinēt tikai publiskiem jeb redzamajiem tipiem. Piemēram, šāds kods beidzas ar "Inconsistent accessibility: parameter type '%1' is less accessible than method '%2'" kļūdu:
class Customer
{
private int id;
}
public static class CustomerExtensions
{
public static void Verify(this Customer c)
{
}
}
- paplašinājuma metodes var tikt klāt vienkāršā veidā privātajam (private) un aizsargātajam (protected) klases saturam, tās ir tādas pašas klases "klientes" kā jebkra cita metode, kas strādā ar klasi ārpus tās. Piemēram, šāds kods beidzas ar "'%1' is inaccessible due to its protection level" kļūdu:
public class Customer
{
private int id;
}
public static class CustomerExtensions
{
public static bool Verify(this Customer c)
{
if (c.id == -1)
{
// new client
}
}
}
- nav iespējams mainīt orģinālā objektā noteiktas instances metodes uzvedību, jo kompilators pēc iespējas vienmēr lietos instances metodi, par ja arī kompilatora redzes lokā būs paplašinājuma metode. Šis gan vairāk ir jāuzskata kā pluss, jo tādā veidā var izvairīties no ļaunprātīgas programmas uzvedības izmaiņas to nepārkompilējot, kas dotu iespēju dažāda veida hijack pasākumiem. Piemēram, šis kods vienmēr atgriezīs "true":
internal class Program
{
private static void Main(string[] args)
{
Customer c = new Customer(1);
Console.WriteLine(c.Verify());
Console.ReadLine();
}
}
public class Customer
{
private int id;
public Customer(int i)
{
this.id = i;
}
public bool Verify()
{
return true;
}
}
public static class CustomerExtensions
{
public static bool Verify(this Customer c)
{
return false;
}
}
- Īsti nepareizi strādā paplašinājuma metodes dispečeris :) Piemēram, šāds kods vienmēr atgriezīs "true":
internal class Program
{
private static void Main(string[] args)
{
Customer c = new Customer();
Console.WriteLine(c.Verify());
Customer c2 = new GoldCustomer();
Console.WriteLine(c2.Verify());
Console.ReadLine();
}
}
public class Customer
{
}
public class GoldCustomer : Customer
{
}
public static class CustomerExtensions
{
public static bool Verify(this Customer c)
{
return true;
}
public static bool Verify(this GoldCustomer c)
{
return false;
}
}
Lai to vērstu par labu, nākas nomainīt rindiņu uz šādu izsaukumu:
Console.WriteLine(((GoldCustomer)c2).Verify());
Paplašinājuma metodes nebūt nenozīmē, ka objekt-orientētās pieejas ir nepareizas. OOP principus ir jāpielieto pēc iespējais vairāk un biežāk, kur tas ir fiziski iespējams, bet paplašinājuma metodes ir hack situācijās, kur esošā koda papildināšana nav iespējama vai arī ir ekonomiski neizdevīga :)
Cerams, ka noderēs!