Back To Future: Dalītās metodes

Pārskatot jaunā C# iespējas, nolēmu atgriezties pie nākotnes pamatprincipiem un aplūkot jaunās iespējas detalizētāk un uzzināt, kas lācītim ir vēdērā.

Šoreiz runa ir par dalītajām metodēm jeb partial methods. Dalītās metodes ļauj konstruēt sava veida paplašinājumu punktus (extension points),piedāvājot iespēju definēt metodes definīciju un ķermeni atsevišķi. Šāda iespēja nodrošina kādai komponentei nodefinēt savu metodes realizāciju, kuri tiek izpildīta komponentē. Šo iespēju izmanto dažāda veida jaunie koda ģeneratori: piemēram LINQ to SQL vai Entity Framework ģenerētie kodi, dodot iespēju izsrādātājam izpildīt kādu kodu dažādos modeļa dzīves cikla momentos. Paplašinājuma metodes iek lietotas gandrīz kā notikumi tomēr tie no izpildes laika ir nedaudz ātrāki. Kāpēc? To apskatīsim tālāk.

Lai uzskatāmā ilestrētu dalīto metožu pielietojumu, apskatīsim kādi pavisam vienkāršu piemēru:

 

namespace PartialsDemo
{
    partial class Partials              // part 1
    {
        private static void Main()
        {
            Do();
        }

        static partial void Do();
    }

    partial class Partials              // part 2
    {
        static partial void Do()
        {
            
        }
    }
}

 

Piemērā nodefinējām klasi ar vienu dalīto metodes definīciju otrā klases daļā nodefinējām metodes realizāciju. Sadlījums protams var būt arī pa fiziskiem failiem. Tātad gala kods izskatītos apmēram šāds:

 

namespace PartialsDemo
{
    class Partials              // part 1
    {
        private static void Main()
        {
            Do();
        }

        static void Do()
        {
        }
    }
}

 

Ja mēs apskatamies sakompilētās klases IL kodu, tad redzam, ka Main metode satur izsaukumu uz Do metodi:

 

.method private hidebysig static void  Main() cil managed
{
  .entrypoint
  // Code size       8 (0x8)
  .maxstack  8
  IL_0000:  nop
  IL_0001:  call       void PartialsDemo.Partials::Do()
  IL_0006:  nop
  IL_0007:  ret
} // end of method Partials::Main

 

Ja turpretim apskatām kodu, kurš nesatur dalītās metodes realizācijas daļu, tad IL kods izskatās šāds:

 

.method private hidebysig static void  Main() cil managed
{
  .entrypoint
  // Code size       2 (0x2)
  .maxstack  8
  IL_0000:  nop
  IL_0001:  ret
} // end of method Partials::Main

 

Uzminiet, kāds būs IL kods šādam izejas kodam:

 

using System;

namespace PartialsDemo
{
    partial class Partials              // part 1
    {
        private static void Main()
        {
            int i = 0;
            Console.Write(i);
            Do(i);
            Console.Write(i);
        }

        static partial void Do(int i);
    }

    partial class Partials              // part 2
    {
        static partial void Do(int i)
        {
        }
    }
}

 

Pareizi, ja būs realizācija Do metodei, tad L kodā ieraudzīsim šīs rindiņas:

 

.method private hidebysig static void  Main() cil managed
{
  .entrypoint
  // Code size       25 (0x19)
  .maxstack  1
  .locals init ([0] int32 i)
  IL_0000:  nop
  IL_0001:  ldc.i4.0
  IL_0002:  stloc.0
  IL_0003:  ldloc.0
  IL_0004:  call       void [mscorlib]System.Console::Write(int32)
  IL_0009:  nop
  IL_000a:  ldloc.0
  IL_000b:  call       void PartialsDemo.Partials::Do(int32)
  IL_0010:  nop
  IL_0011:  ldloc.0
  IL_0012:  call       void [mscorlib]System.Console::Write(int32)
  IL_0017:  nop
  IL_0018:  ret
} // end of method Partials::Main

 

Izceltais IKL kods ir Do operācijas izsaukums. Insrukciju "nop" var ignorēt par cik tas ir neoptimizēts atkļūdošanas režīmā kompilēts kods, kas nodrošina pārtraukumpunktu (breakpoint) ievietošanu par uz rindiņu, kurā atrodas sistēmas iekava.

Turpretim kods, kas nesatur Do operācijas realizāciju tiek kompilēts uz šādu IL kodu:

 

.method private hidebysig static void  Main() cil managed
{
  .entrypoint
  // Code size       18 (0x12)
  .maxstack  1
  .locals init ([0] int32 i)
  IL_0000:  nop
  IL_0001:  ldc.i4.0
  IL_0002:  stloc.0
  IL_0003:  ldloc.0
  IL_0004:  call       void [mscorlib]System.Console::Write(int32)
  IL_0009:  nop
  IL_000a:  ldloc.0
  IL_000b:  call       void [mscorlib]System.Console::Write(int32)
  IL_0010:  nop
  IL_0011:  ret
} // end of method Partials::Main

 

Kā redzmas, tas kompilētajā kodā nemaz neparādās Do operācijas izsaukums.

Kopsummā redzams, ka šis paņēmiens ir līdzīgs ConditionalAttribute pielietojumam, kad kods tiek kompilēts tikai tad, ja ir definēta kāda noteikta kopilācijas laika konstante (#define direktīva vai /define parametrs kopilatoram).

 

#define FOO

#region

using System;
using System.Diagnostics;

#endregion

namespace PartialsDemo
{
    internal class Partials
    {
        private static void Main()
        {
            int i = 0;
            Console.WriteLine(i);
            Do(i);
            Console.WriteLine(i);
        }

        [Conditional("FOO")]
        private static void Do(int i)
        {
            Console.WriteLine(" *** " + i);
        }
    }
}

 

Ģenrētais IL kods šādam izejas kodam ir stipri līdzīgs dalīto metožu ģenerētajam kodam:

 

.method private hidebysig static void  Main() cil managed
{
  .entrypoint
  // Code size       25 (0x19)
  .maxstack  1
  .locals init ([0] int32 i)
  IL_0000:  nop
  IL_0001:  ldc.i4.0
  IL_0002:  stloc.0
  IL_0003:  ldloc.0
  IL_0004:  call       void [mscorlib]System.Console::WriteLine(int32)
  IL_0009:  nop
  IL_000a:  ldloc.0
  IL_000b:  call       void PartialsDemo.Partials::Do(int32)
  IL_0010:  nop
  IL_0011:  ldloc.0
  IL_0012:  call       void [mscorlib]System.Console::WriteLine(int32)
  IL_0017:  nop
  IL_0018:  ret
} // end of method Partials::Main

 

Tomēr ConditionalAttribute ir viena nianse: arī ja netiek nodefinēta kopilācijas laika konstante:

 

.method private hidebysig static void  Main() cil managed
{
  .entrypoint
  // Code size       18 (0x12)
  .maxstack  1
  .locals init ([0] int32 i)
  IL_0000:  nop
  IL_0001:  ldc.i4.0
  IL_0002:  stloc.0
  IL_0003:  ldloc.0
  IL_0004:  call       void [mscorlib]System.Console::WriteLine(int32)
  IL_0009:  nop
  IL_000a:  ldloc.0
  IL_000b:  call       void [mscorlib]System.Console::WriteLine(int32)
  IL_0010:  nop
  IL_0011:  ret
} // end of method Partials::Main

 

netiek izsaukta Do operācija, bet tomēr sitēmā tā fiziski atrodas:

 

 

Ar šādu IL  kodu:

 

.method private hidebysig static void  Do(int32 i) cil managed
{
  .custom instance void [mscorlib]System.Diagnostics.ConditionalAttribute::.ctor(string) = ( 01 00 03 46 4F 4F 00 00 )                         // ...FOO..
  // Code size       24 (0x18)
  .maxstack  8
  IL_0000:  nop
  IL_0001:  ldstr      " *** "
  IL_0006:  ldarg.0
  IL_0007:  box        [mscorlib]System.Int32
  IL_000c:  call       string [mscorlib]System.String::Concat(object,
                                                              object)
  IL_0011:  call       void [mscorlib]System.Console::WriteLine(string)
  IL_0016:  nop
  IL_0017:  ret
} // end of method Partials::Do

 

Plašāks pielietojums šai ConditionalAttribute pieejai ir Debug klase, ko izmanto dažāda veida pārbaudēm atkļūdošanas režīmā. Izsaukumi tiek kopilēti tikai tad, ja ir definēta DEBUG konstante, pretējā gadījumā visi izsaukumi tiek dzēsti no koda.

Dalītās metodes tiešām ir dalītas un to izsaukumi tiek pilnībā dzēsti no koda, ja nav definēta tās realizācija.

 

Tomēr dalītajām metodēm ir pāris ierobežojumi:

  • tās vienmēr ir privātas

Šāds kods nekopilēsies un paziņos kļūdu (error CS0750: A partial method cannot have access modifiers or the virtual, abstract, override, new, sealed, or extern modifiers):

 

partial class Partials              // part 1
{
    private static void Main()
    {
        Do();
    }

    public static partial void Do();
}

Kas ir arī saprotams, jo ja dalītās klases pieļautu iespēju būt publiski redzamām, tad situācijās, kad realizācija metodei nav norādīta, tā nemaz netiek kompilēta kodā un arējais publiskais izsaukums nemaz neizpildīsies.

 

  • tāpat uz dalītajām metodēm nevar izveidot delegātus

Šāds kods nekopilēsies ar kļūdu (CS0762: Cannot create delegate from method %1 because it is a partial method without an implementing declaration):

 

partial class Main
{
    public void Execute()
    {
        new Thread(Do).Start();
    }

    partial void Do();
}

 

  • tām vienmēr atgrieztā vērtība būs void

Šāds kods nekopilēsies un paziņos kļūdu (CS0766: Partial methods must have a void return type).

 

partial class Partials              // part 1
{
    private static void Main()
    {
        Do();
    }

    static partial int Do();
}

 

Iemesls ir pavisam vienkāršs... Kāds būtu šāds koda gala rezultāts?

 

int i = Main.Do();
int j = i + 10;

 

  • tā paša iemesla dēļ dalītajām metodēm nevar būt out parametri

Šāda koda kopilācijas beigsies ar error CS0752: A partial method cannot have out parameters.

 

partial class Partials              // part 1
{
    private static void Main()
    {
        int i;
        Do(i);
    }

    static partial void Do(out int i);
}

 

Ja mēs salīdzinām ar notikumu (events) un delegātu (delegates) infrastruktūru, piemēram, šāds kods izpildās 152 ms:

 

internal class Events
{
    private static void Main()
    {
        Stopwatch sw = new Stopwatch();
        sw.Start();

        for (int i = 0; i < 1000000; i++)
        {
            Foo f = new Foo();
            f.OnDoCallback += delegate { };
            f.Do();
        }

        sw.Stop();
        Console.WriteLine(sw.ElapsedMilliseconds);
        Console.ReadLine();
    }
}

internal delegate void Callback();

internal class Foo
{
    public void Do()
    {
        if (this.OnDoCallback != null)
        {
            this.OnDoCallback();
        }
    }

    public event Callback OnDoCallback;
}

 

Un, pielāgojot to dalītajām metodēm, kods izpildās daudz ātrāk (dotajā gadījumā tas izpildījās 38ms):

 

internal class Events
{
    private static void Main()
    {
        Stopwatch sw = new Stopwatch();
        sw.Start();

        for (int i = 0; i < 1000000; i++)
        {
            Foo f = new Foo();
            f.Do();
        }

        sw.Stop();
        Console.WriteLine(sw.ElapsedMilliseconds);
        Console.ReadLine();
    }
}


internal partial class Foo
{
    partial void Callback()
    {
    }
}

internal partial class Foo
{
    partial void Callback();
    public void Do()
    {
        Callback();
    }
}

 

Kā redzams, tad notikumi un delegāti ir daudz lēnāki izpildes režīmā kopā ar savu delegātu overheadu salīdzinoši ar vienkāršu metodes izsaukumu attiecīgajā vietā. Tomēr tos nevar viennozīmīgi salīdzināt ar dalītajām metodēm, jo notikumu infrastruktūra ir daudz elastīgāka un to var publicēt ārpus klases robežām.

 

 

Cerams, ka noderēs!

Published Saturday, September 06, 2008 3:03 PM by valdis.iljuconoks
Filed under: ,

Comments

# re: Back To Future: Dalītās metodes

Interesanti, biju palaidis garām, ka pastāv tāda iespēja. Domāju, ka konstrukcija ir  visai ierobežots pielietojumu, un reti kur to varēs novērot.

Sunday, September 07, 2008 6:13 PM by andrejs.mamontovs

# re: Back To Future: Dalītās metodes

redzu pielietojumu tikai ģenerētajā kodā, kuru ģenerētājs dod iespēju paplašināt vienā noteiktā veidā tikai un kas ir ātrāks no izpildes redzes punkta par notikumu infrastruktūru...

Sunday, September 07, 2008 9:27 PM by valdis.iljuconoks

# re: Back To Future: Dalītās metodes

Wow, this is in every resepct what I needed to know.

Friday, July 15, 2011 2:18 AM by Gloriane

# re: Back To Future: Dalītās metodes

Fuerlraz? That's marvelously good to know.

Friday, July 15, 2011 8:33 PM by Barbi

# re: Back To Future: Dalītās metodes

This is ecxtaly what I was looking for. Thanks for writing!

Friday, July 15, 2011 8:33 PM by Leaidan

# Interesting, thanks

Everything is extremely open and quite clear explanation of troubles. was truly info. Your internet site is really useful. Many thanks for sharing.

Wednesday, November 16, 2011 6:00 PM by Nicolasa Maud

Leave a Comment

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