C # 'daki olayları ve olay işleyicilerini anlama


330

Olayların amacını, özellikle kullanıcı arayüzleri oluşturma bağlamında anlıyorum. Bu bir etkinlik oluşturmak için prototip olduğunu düşünüyorum:

public void EventName(object sender, EventArgs e);

Olay işleyicileri ne yapar, neden gereklidirler ve nasıl bir tane oluştururum?


10
@Andy tarafından belirtildiği gibi, burada kod snippet'i etkinliğin kendisine değil, etkinliğe kayıtlı yöntemi açıklar.
dthrasher


Yanıtlar:


661

Olay işleyicileri anlamak için delegeleri anlamanız gerekir . Olarak C # , bir yöntem gösteren bir işaretçi (veya bir referans) gibi bir temsilci düşünebiliriz. İşaretçi bir değer olarak iletilebildiğinden bu yararlıdır.

Temsilcinin merkezi konsepti imzası veya şeklidir. Yani (1) dönüş tipi ve (2) girdi argümanları. Biz temsilci oluşturun Örneğin, void MyDelegate(object sender, EventArgs e)bu sadece hangi dönüş yöntemlerine işaret edebilir voidve bir almak objectve EventArgs. Bir kare delik ve bir kare dübel gibi. Bu yöntemlerin delege ile aynı imzayı veya şekli olduğunu söylüyoruz.

Bu nedenle, bir yönteme nasıl referans oluşturulacağını bilerek, olayların amacını düşünelim: sistemde başka bir yerde bir şey olduğunda veya "olayı işlerken" bazı kodların yürütülmesine neden olmak istiyoruz. Bunu yapmak için, yürütülmesini istediğimiz kod için özel yöntemler oluştururuz. Etkinlik ve uygulanacak yöntemler arasındaki tutkal delegelerdir. Olay, olay oluşturulduğunda çağrılacak yöntemlere işaretçilerin bir "listesini" saklamalıdır. * Tabii ki, bir yöntemi çağırabilmek için, ona hangi argümanların iletileceğini bilmemiz gerekir! Temsilciyi etkinlik ve çağrılacak tüm özel yöntemler arasındaki "sözleşme" olarak kullanırız.

Böylece varsayılan EventHandler(ve buna benzer birçok yöntem ) belirli bir yöntem şeklini temsil eder (yine void / object-EventArgs). Bir olayı bildirdiğinizde , bir temsilci belirterek o olayın hangi yöntem şeklini (EventHandler) çağıracağını söylersiniz:

//This delegate can be used to point to methods
//which return void and take a string.
public delegate void MyEventHandler(string foo);

//This event can cause any method which conforms
//to MyEventHandler to be called.
public event MyEventHandler SomethingHappened;

//Here is some code I want to be executed
//when SomethingHappened fires.
void HandleSomethingHappened(string foo)
{
    //Do some stuff
}

//I am creating a delegate (pointer) to HandleSomethingHappened
//and adding it to SomethingHappened's list of "Event Handlers".
myObj.SomethingHappened += new MyEventHandler(HandleSomethingHappened);

//To raise the event within a method.
SomethingHappened("bar");

(* Bu, .NET'teki olayların anahtarıdır ve "büyüyü" soyar - bir olay, kapakların altında, sadece aynı "şekil" in yöntemlerinin bir listesidir. Liste, olayın yaşadığı yerde saklanır. olay "yükseltildi", bu gerçekten sadece "bu yöntem listesini gözden geçirin ve bu değerleri parametre olarak kullanarak her birini çağırın". çağrılacak).


24
Ve şimdi herkes neden EventHandler denir açıklayabilir ?? Tüm kafa karıştırıcı adlandırma sözleşmelerinden, bu en kötü ...
Joel Gö

37
@Joel in Go etkinliğe EventHandler adı verilmez - EventHandler, etkinliğin kendisiyle iletişim kuran herkesle olması gereken sözleşmedir. "String MyString" gibi - dize türü bildiriyor. event MyEventHandler TheEvent, bu olayla etkileşime giren herkesin MyEventHandler sözleşmesine uyması gerektiğini bildiriyor. İşleyici sözleşmesi, sözleşmenin öncelikle olayın nasıl ele alınacağını tanımlamasıdır.
Rex M

18
Olay nasıl işlenir?
simya

17
@Rex M: Gördüğüm "MyEventHandler" için ilk tutarlı açıklama için teşekkür ederim :)
Joel Gö

10
Aşama için teşekkür ederim: "Etkinlik ve uygulanacak yöntemler arasındaki tutkal delegelerdir.", Bu gerçekten harika.
zionpi

103

C # iki terimi bilir delegateve event. Birincisi ile başlayalım.

Temsilci

A delegate, bir yönteme referanstır. Tıpkı bir örneğe referans oluşturabileceğiniz gibi:

MyClass instance = myFactory.GetInstance();

Bir yönteme başvuru oluşturmak için bir temsilci kullanabilirsiniz:

Action myMethod = myFactory.GetInstance;

Artık bir yönteme bu başvurunuz olduğuna göre, yöntemi başvuru yoluyla çağırabilirsiniz:

MyClass instance = myMethod();

Ama neden ki? Ayrıca myFactory.GetInstance()doğrudan arayabilirsiniz . Bu durumda yapabilirsiniz. Bununla birlikte, uygulamanın geri kalanının bilgi sahibi olmasını myFactoryveya myFactory.GetInstance()doğrudan aramasını istemediğiniz yerleri düşünmeniz gereken birçok durum vardır .

Değiştirmeye muktedir istiyorsanız bariz biridir myFactory.GetInstance()içine myOfflineFakeFactory.GetInstance()bir merkezi bir konum (aka gelen fabrika yöntemi desen ).

Fabrika yöntemi desen

Yani, bir TheOtherClasssınıfınız varsa ve kullanması gerekiyorsa myFactory.GetInstance(), kod delegeler olmadan böyle görünecektir ( TheOtherClasstürünüzü bilmeniz gerekir myFactory):

TheOtherClass toc;
//...
toc.SetFactory(myFactory);


class TheOtherClass
{
   public void SetFactory(MyFactory factory)
   {
      // set here
   }

}

Temsilci kullanırsanız fabrikamın türünü göstermeniz gerekmez:

TheOtherClass toc;
//...
Action factoryMethod = myFactory.GetInstance;
toc.SetFactoryMethod(factoryMethod);


class TheOtherClass
{
   public void SetFactoryMethod(Action factoryMethod)
   {
      // set here
   }

}

Böylece, başka bir sınıfa başka bir sınıfa verebilirsiniz, türünüzü bunlara maruz bırakmadan. Gösterdiğiniz tek şey, yönteminizin imzasıdır (kaç parametreniz ve benzeri).

"Metodumun imzası", bunu daha önce nereden duydum? O evet, arayüzler !!! arayüzler bütün bir sınıfın imzasını tanımlar. Delegeleri sadece bir yöntemin imzasını tanımlayan olarak düşünün!

Bir arabirim ve bir temsilci arasındaki bir diğer büyük fark, sınıfınızı yazarken C # 'a "bu yöntem bu tür bir temsilci uygular" demek zorunda kalmamanızdır. Arabirimlerle, "bu sınıf bu tür bir arabirimi uygular" demeniz gerekir.

Ayrıca, bir temsilci başvurusu (bazı kısıtlamalarla, aşağıya bakın) birden çok yönteme (çağrılan MulticastDelegate) başvurabilir . Bu, temsilci çağırdığınızda, açıkça eklenmiş birden çok yöntem yürütüleceği anlamına gelir. Bir nesne başvurusu her zaman yalnızca bir nesneye gönderme yapabilir.

Bir yönelik kısıtlamalar MulticastDelegate(yöntem / temsilci) imza herhangi dönüş değeri (olmaması gerektiğini vardır void) ve anahtar kelimeler outve refimza kullanılmamaktadır. Açıkçası, bir sayı döndüren ve aynı sayıyı döndürmelerini bekleyen iki yöntemi çağıramazsınız. İmza uygun olduğunda, temsilci otomatik olarak a MulticastDelegate.

Etkinlik

Olaylar, yalnızca diğer nesnelerden delege aboneliğini açığa çıkaran özelliklerdir (get; set; özellikler örnek alanlarına özellikler). Ancak bu özellikler get; set; özelliğini desteklemez. Bunun yerine eklemeyi desteklerler; Kaldırmak;

Böylece sahip olabilirsiniz:

    Action myField;

    public event Action MyProperty
    {
        add { myField += value; }
        remove { myField -= value; }
    }

Kullanıcı arayüzünde kullanım (WinForms, WPF, UWP vb.)

Şimdi, bir temsilci bir yönteme referans olduğunu ve dünyaya bize temsilcilerimizden referans alınacak yöntemlerini verebileceklerini bildirecek bir etkinliğe sahip olabileceğimizi biliyoruz ve biz bir UI düğmesiyiz, o zaman: biz tıklanıp tıklanmadığımla ilgilenen herkese yöntemlerini bize kaydettirmelerini isteyebiliriz (maruz kaldığımız etkinlik aracılığıyla). Bize verilen tüm yöntemleri kullanabilir ve temsilcilerimiz tarafından referans gösterebiliriz. Ve sonra, bekleyip bekleyeceğiz .... bir kullanıcı gelip bu düğmeyi tıklayana kadar, temsilci çağırmak için yeterli nedenimiz olacak. Temsilci bize verilen tüm bu yöntemlere atıfta bulunduğundan, tüm bu yöntemler çağrılır. Bu yöntemlerin ne yaptığını veya bu sınıfın hangi sınıfın uygulandığını bilmiyoruz. Tek umursadığımız biri bizi tıklamakla ilgilendiğiydi,

Java

Java gibi delegelere sahip değildir. Bunun yerine arayüzler kullanıyorlar. Bunu yapmanın yolu, 'bizi tıklamakla' ilgilenen herkesten belirli bir arabirimi (arayabileceğimiz belirli bir yöntemle) uygulamalarını istemek, sonra bize arabirimi uygulayan tüm örneği vermektir. Bu arayüzü uygulayan tüm nesnelerin bir listesini tutarız ve tıkladığımızda 'arayabileceğimiz belirli yöntemleri' çağırabiliriz.


açıklama için tezahürat yapar, ancak bir etkinliğin aboneleri devralan bir temsilci örneğinden farkı nedir? ikisi de aynı şey gibi mi görünüyor?
BKSpurgeon

Onlar çünkü var @BKSpurgeon olan "abonelere almak delegeler" - eventsadece sözdizimi şeker, başka bir şey değildir.
Mathieu Guindon

"MulticastDelegate için kısıtlamalar (yöntem / delege) imzasının herhangi bir dönüş değeri (geçersiz) olmamalıdır", bunun doğru olduğunu düşünmüyorum. Dönüş değerleri varsa, son değeri döndürür.
Hozikimaru

"Böylece, başka bir sınıfa başka bir sınıfa verebilirsiniz, türünüzü bunlara maruz bırakmadan. Maruz bıraktığınız tek şey yönteminizin imzasıdır ..." - bu benim için kritik nokta. Teşekkür ederim!
Ryan

40

Bu aslında bir olay işleyicisinin bildirimidir - bir olay tetiklendiğinde çağrılacak bir yöntem. Bir etkinlik oluşturmak için şöyle bir şey yazarsınız:

public class Foo
{
    public event EventHandler MyEvent;
}

Daha sonra etkinliğe şu şekilde abone olabilirsiniz:

Foo foo = new Foo();
foo.MyEvent += new EventHandler(this.OnMyEvent);

OnMyEvent () şöyle tanımlanır:

private void OnMyEvent(object sender, EventArgs e)
{
    MessageBox.Show("MyEvent fired!");
}

Ne zaman Fooateş ederse MyEvent, OnMyEventişleyiciniz aranacaktır.

Her zaman EventArgsikinci parametre olarak bir örneğini kullanmanız gerekmez . Ek bilgi eklemek istiyorsanız, türetilmiş bir sınıfı kullanabilirsiniz EventArgs( kuralın EventArgstemelidir). Örneğin, ControlWinForms'da veya WPF'de tanımlanan olaylardan bazılarına bakarsanız FrameworkElement, olay işleyicilerine ek bilgi ileten olayların örneklerini görebilirsiniz.


14
Soruyu cevapladığınız ve Delegeler ve Etkinliklere katılmadığınız için teşekkür ederiz.
divide_byzero

3
OnXXXOlay işleyiciniz için adlandırma desenini kullanmamanızı öneririz . (Aptalca, OnXXX, MFC'de 'XXX'yi işlemek' ve .net'te 'XXX'u yükseltmek' anlamındadır ve şimdi anlamı belirsiz ve kafa karıştırıcıdır - ayrıntılar için bu gönderiye bakın ). Tercih edilen adlar RaiseXXXolayları yükseltmek ve HandleXXXveya Sender_XXXolay işleyicileri için olacaktır.
Jason Williams

1
Basit bir WinForms uygulamasıyla çalışan bir örnek gösterebilir misiniz?
MC9000

40

İşte size yardımcı olabilecek bir kod örneği:

using System;
using System.Collections.Generic;
using System.Text;

namespace Event_Example
{
  // First we have to define a delegate that acts as a signature for the
  // function that is ultimately called when the event is triggered.
  // You will notice that the second parameter is of MyEventArgs type.
  // This object will contain information about the triggered event.

  public delegate void MyEventHandler(object source, MyEventArgs e);

  // This is a class which describes the event to the class that receives it.
  // An EventArgs class must always derive from System.EventArgs.

  public class MyEventArgs : EventArgs
  {
    private string EventInfo;

    public MyEventArgs(string Text) {
      EventInfo = Text;
    }

    public string GetInfo() {
      return EventInfo;
    }
  }

  // This next class is the one which contains an event and triggers it
  // once an action is performed. For example, lets trigger this event
  // once a variable is incremented over a particular value. Notice the
  // event uses the MyEventHandler delegate to create a signature
  // for the called function.

  public class MyClass
  {
    public event MyEventHandler OnMaximum;

    private int i;
    private int Maximum = 10;

    public int MyValue
    {
      get { return i; }
      set
      {
        if(value <= Maximum) {
          i = value;
        }
        else 
        {
          // To make sure we only trigger the event if a handler is present
          // we check the event to make sure it's not null.
          if(OnMaximum != null) {
            OnMaximum(this, new MyEventArgs("You've entered " +
              value.ToString() +
              ", but the maximum is " +
              Maximum.ToString()));
          }
        }
      }
    }
  }

  class Program
  {
    // This is the actual method that will be assigned to the event handler
    // within the above class. This is where we perform an action once the
    // event has been triggered.

    static void MaximumReached(object source, MyEventArgs e) {
      Console.WriteLine(e.GetInfo());
    }

    static void Main(string[] args) {
      // Now lets test the event contained in the above class.
      MyClass MyObject = new MyClass();
      MyObject.OnMaximum += new MyEventHandler(MaximumReached);
      for(int x = 0; x <= 15; x++) {
        MyObject.MyValue = x;
      }
      Console.ReadLine();
    }
  }
}

4
C # 6'daki temsilci delege basitleştirilebilir:OnMaximum?.Invoke(this,new MyEventArgs("you've entered..."));
Tim Schmelter

23

Sadece burada mevcut büyük cevaplar eklemek için - kabul edilen bir kod üzerinde bina, bir delegate void MyEventHandler(string foo)...

Derleyici, SomethingHappened olayının temsilci türünü bildiğinden , bu:

myObj.SomethingHappened += HandleSomethingHappened;

Tamamen şuna eşittir:

myObj.SomethingHappened += new MyEventHandler(HandleSomethingHappened);

Ve işleyicileri de olabilir kayıtsız ile -=böyle:

// -= removes the handler from the event's list of "listeners":
myObj.SomethingHappened -= HandleSomethingHappened;

Tamlık uğruna, etkinliği artırmak, sadece olayın sahibi olan sınıfta şu şekilde yapılabilir:

//Firing the event is done by simply providing the arguments to the event:
var handler = SomethingHappened; // thread-local copy of the event
if (handler != null) // the event is null if there are no listeners!
{
    handler("Hi there!");
}

İşleyicinin iş parçacığının yerel olarak kopyalandığından emin olmak için iş parçacığının yerel kopyası gerekir - aksi takdirde bir iş parçacığı gidip olayı kontrol edip hemen kontrol ettikten hemen sonra son işleyicinin kaydını kaldırabilir ve orada nullbir "eğlence" olurdu NullReferenceException.


C # 6 bu desen için güzel bir kısa el tanıttı. Boş yayılma işlecini kullanır.

SomethingHappened?.Invoke("Hi there!");

13

Olayları anlamam;

Temsilci:

Yürütülecek yönteme / yöntemlere başvuruda bulunacak bir değişken. Bu, değişken gibi yöntemlerin aktarılmasını mümkün kılar.

Etkinliği oluşturma ve arama adımları:

  1. Etkinlik bir temsilci örneğidir

  2. Bir etkinlik bir temsilci örneği olduğundan, önce temsilciyi tanımlamamız gerekir.

  3. Olay tetiklendiğinde yürütülecek yöntemi / yöntemleri atayın ( Temsilciyi arama )

  4. Etkinliği başlat ( Delege çağırın )

Misal:

using System;

namespace test{
    class MyTestApp{
        //The Event Handler declaration
        public delegate void EventHandler();

        //The Event declaration
        public event EventHandler MyHandler;

        //The method to call
        public void Hello(){
            Console.WriteLine("Hello World of events!");
        }

        public static void Main(){
            MyTestApp TestApp = new MyTestApp();

            //Assign the method to be called when the event is fired
            TestApp.MyHandler = new EventHandler(TestApp.Hello);

            //Firing the event
            if (TestApp.MyHandler != null){
                TestApp.MyHandler();
            }
        }

    }   

}

3

yayıncı: olayların gerçekleştiği yer. Yayıncı sınıfın hangi temsilci kullandığını belirtmeli ve gerekli bağımsız değişkenleri oluşturmalı, bu bağımsız değişkenleri ve kendisini temsilciye aktarmalıdır.

abone: yanıtın gerçekleştiği yer. Abone olayları yanıtlamak için yöntemler belirtmelidir. Bu yöntemler, temsilci ile aynı türde argümanlar almalıdır. Abone daha sonra bu yöntemi yayıncının temsilcisine ekler.

Bu nedenle, olay yayıncıda gerçekleştiğinde, temsilci bazı olay bağımsız değişkenleri (veriler vb.) Alacaktır, ancak yayıncının tüm bu verilerle ne olacağı hakkında hiçbir fikri yoktur. Aboneler yayıncı sınıfındaki olaylara yanıt vermek için kendi sınıflarında yöntemler oluşturabilir, böylece aboneler yayıncının olaylarına yanıt verebilir.


2
//This delegate can be used to point to methods
//which return void and take a string.
public delegate void MyDelegate(string foo);

//This event can cause any method which conforms
//to MyEventHandler to be called.
public event MyDelegate MyEvent;

//Here is some code I want to be executed
//when SomethingHappened fires.
void MyEventHandler(string foo)
{
    //Do some stuff
}

//I am creating a delegate (pointer) to HandleSomethingHappened
//and adding it to SomethingHappened's list of "Event Handlers".
myObj.MyEvent += new MyDelegate (MyEventHandler);

0

KE50 ile aynı fikirdeyim, ancak 'event' anahtar kelimesini 'ActionCollection' için bir takma ad olarak görüyorum çünkü etkinlik gerçekleştirilecek eylemlerin bir koleksiyonunu (yani temsilci) barındırıyor.

using System;

namespace test{

class MyTestApp{
    //The Event Handler declaration
    public delegate void EventAction();

    //The Event Action Collection 
    //Equivalent to 
    //  public List<EventAction> EventActions=new List<EventAction>();
    //        
    public event EventAction EventActions;

    //An Action
    public void Hello(){
        Console.WriteLine("Hello World of events!");
    }
    //Another Action
    public void Goodbye(){
        Console.WriteLine("Goodbye Cruel World of events!");
    }

    public static void Main(){
        MyTestApp TestApp = new MyTestApp();

        //Add actions to the collection
        TestApp.EventActions += TestApp.Hello;
        TestApp.EventActions += TestApp.Goodbye;

        //Invoke all event actions
        if (TestApp.EventActions!= null){
            //this peculiar syntax hides the invoke 
            TestApp.EventActions();
            //using the 'ActionCollection' idea:
            // foreach(EventAction action in TestApp.EventActions)
            //     action.Invoke();
        }
    }

}   

}

0

Gönderide harika teknik cevaplar! Teknik olarak buna eklenecek hiçbir şeyim yok .

Genel olarak diller ve yazılımlarda yeni özelliklerin ortaya çıkmasının ana nedenlerinden biri pazarlama veya şirket politikalarıdır! :-) Bu tahmin altında olmamalıdır!

Bunun delegeler ve etkinlikler için de belirli ölçüde geçerli olduğunu düşünüyorum! ben onları yararlı bulmak ve C # diline değer katmak, ancak Öte yandan Java dili bunları kullanmaya karar verdi! delegelerle ne çözüyor olursanız olun, dilin mevcut özellikleriyle (ör.

Şimdi 2001 civarında Microsoft, .NET çerçevesini ve C # dilini Java'ya rakip bir çözüm olarak yayınladı, bu nedenle Java'nın sahip olmadığı YENİ ÖZELLİKLER olması iyi oldu.


0

Geçenlerde c # olayları nasıl kullanılacağına bir örnek yaptım ve bunu bloguma gönderdim. Çok basit bir örnekle olabildiğince netleştirmeye çalıştım. Herkese yardımcı olabilirse, işte burada: http://www.konsfik.com/using-events-in-csharp/

Açıklama ve kaynak kodu (çok sayıda yorum içeren) içerir ve temel olarak olayların ve olay işleyicilerin uygun (şablon benzeri) kullanımına odaklanır.

Bazı önemli noktalar:

  • Olaylar "delegelerin alt türleri" gibidir, sadece daha kısıtlıdır (iyi bir şekilde). Aslında bir olayın beyanı her zaman bir temsilci içerir (EventHandlers bir tür temsilci).

  • Olay İşleyicileri, kullanıcıyı belirli bir "imzası" olan etkinlikler oluşturmaya zorlayan belirli delege türleridir (bunları şablon olarak düşünebilirsiniz). İmza şu biçimdedir: (nesne gönderen, EventArgs eventarguments).

  • Etkinliğin iletmesi gereken her tür bilgiyi dahil etmek için kendi EventArgs alt sınıfınızı oluşturabilirsiniz. Olayları kullanırken EventHandlers kullanmak gerekli değildir. Onları tamamen atlayabilir ve kendi delegelerinizi yerinde kullanabilirsiniz.

  • Etkinliklerin ve delegelerin kullanılması arasındaki önemli farklardan biri, etkinliklerin herkese açık olarak bildirilse bile yalnızca bildirildikleri sınıftan çağrılabilmeleridir. Bu çok önemli bir ayrımdır, çünkü olaylarınızın dış yöntemlere “bağlı” olmaları ve aynı zamanda “harici yanlış kullanım” dan korunmaları için maruz kalmasına izin verir.


0

Bilmeniz gereken başka bir şey , bazı durumlarda, düşük bir kuplaj seviyesine ihtiyacınız olduğunda Delegeleri / Etkinlikleri kullanmanız gerekir !

Bir bileşeni uygulamada birden fazla yerde kullanmak istiyorsanız , düşük kuplaj seviyesine sahip bir bileşen yapmanız gerekir ve ilgili ilgisiz MANTIK , bileşeninizin DIŞINDA devredilmelidir ! Bu, ayrılmış bir sisteme ve daha temiz bir koda sahip olmanızı sağlar.

Gelen KATI prensipte bu "bir D ", ( D ependency ters ilişki ilkesi).

" IoC " olarak da bilinir , Konversiyon kontrolü .

Etkinlikler, Delegeler ve DI (Bağımlılık Enjeksiyonu) ile " IoC " yapabilirsiniz .

Bir alt sınıftaki bir yönteme erişmek kolaydır. Ancak çocuktan bir ebeveyn sınıfındaki bir yönteme erişmek daha zordur. Ebeveyn referansını çocuğa iletmelisiniz! (veya DI ile Arayüz kullanın)

Delegeler / Etkinlikler referans olarak çocuktan ebeveyne iletişim kurmamızı sağlar!

resim açıklamasını buraya girin

Yukarıdaki bu şemada, Temsilci / Olay kullanmıyorum ve ana bileşen B'nin , A yönteminde kayıtsız iş mantığını yürütmek için ana bileşen A'nın bir referansına sahip olması gerekir (yüksek bağlantı düzeyi).

Bu yaklaşımla, B bileşenini kullanan tüm bileşenlerin tüm referanslarını koymak zorundayım! :(

resim açıklamasını buraya girin

Yukarıdaki bu şemada, Temsilci / Olay kullanıyorum ve B bileşeninin A'yı tanıması gerekmiyor. (Düşük bağlantı seviyesi)

Ve B bileşeninizi uygulamanızın herhangi bir yerinde kullanabilirsiniz !

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.