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!

Published Saturday, September 20, 2008 3:35 PM by valdis.iljuconoks
Filed under: ,

Comments

# re: Back To Future: Paplašinājuma metodes

+2 centi par objektorientēto pieeju. tas protams viss ir jauki un smuki, bet tomēr sarežģītās vai determinētās situācijās labāk un efektīvāk tomēr ir izmantot kādu no funkcionālās programmēšanas iespējām (piemēram ef'šarp - f#). par to noteikti tuvākā laikā nāks klajā vairāki raksti...

Friday, October 10, 2008 1:46 PM by valdis.iljuconoks

# IEmsYqoZQEpKv

Wzpo8G Thanks-a-mundo for the article post.Really thank you! Keep writing.

Thursday, March 22, 2012 8:26 PM by buy google plus

Leave a Comment

(obligāts) 
(obligāts) 
(brīvizvēles)
(obligāts)