ยกเลิกการสมัครสมาชิกแบบไม่ระบุชื่อใน C #


222

เป็นไปได้ไหมที่จะยกเลิกการสมัครสมาชิกโดยไม่ระบุชื่อจากกิจกรรม?

ถ้าฉันสมัครสมาชิกกิจกรรมเช่นนี้:

void MyMethod()
{
    Console.WriteLine("I did it!");
}

MyEvent += MyMethod;

ฉันสามารถยกเลิกการสมัครสมาชิกเช่นนี้:

MyEvent -= MyMethod;

แต่ถ้าฉันสมัครโดยใช้วิธีไม่ระบุชื่อ:

MyEvent += delegate(){Console.WriteLine("I did it!");};

เป็นไปได้ไหมที่จะยกเลิกการสมัครสมาชิกโดยไม่ระบุชื่อ ถ้าเป็นเช่นนั้นได้อย่างไร


4
สำหรับเหตุผลที่คุณไม่สามารถทำสิ่งนี้ได้: stackoverflow.com/a/25564492/23354
Marc Gravell

คำตอบ:


230
Action myDelegate = delegate(){Console.WriteLine("I did it!");};

MyEvent += myDelegate;


// .... later

MyEvent -= myDelegate;

เพียงแค่ทำการอ้างอิงถึงผู้ได้รับมอบหมายรอบ ๆ


141

เทคนิคหนึ่งคือการประกาศตัวแปรเพื่อเก็บเมธอดนิรนามซึ่งจะพร้อมใช้งานภายในเมธอดแบบไม่ระบุชื่อเอง สิ่งนี้ใช้ได้กับฉันเพราะพฤติกรรมที่ต้องการคือยกเลิกการสมัครหลังจากจัดการเหตุการณ์แล้ว

ตัวอย่าง:

MyEventHandler foo = null;
foo = delegate(object s, MyEventArgs ev)
    {
        Console.WriteLine("I did it!");
        MyEvent -= foo;
    };
MyEvent += foo;

1
การใช้รหัสประเภทนี้ Resharper บ่นเกี่ยวกับการเข้าถึงการปิดการแก้ไข ... วิธีการนี้เชื่อถือได้หรือไม่ ฉันหมายถึงเราแน่ใจหรือไม่ว่าตัวแปร 'foo' ภายในเนื้อความของวิธีการไม่ระบุชื่อจริงๆอ้างอิงถึงวิธีการไม่ระบุตัวตนจริง ๆ หรือไม่
BladeWise

7
ฉันพบคำตอบสำหรับ dubt ของฉันและนั่นคือ 'foo' จะถือการอ้างอิงถึงวิธีการที่ไม่ระบุชื่ออย่างแท้จริง ตัวแปรที่ดักจับถูกแก้ไขเนื่องจากถูกดักจับก่อนที่เมธอดที่ไม่ระบุชื่อจะถูกกำหนดให้กับมัน
BladeWise

2
นั่นคือสิ่งที่ฉันต้องการ! ฉันหายไป = null (MyEventHandler foo = ผู้แทน {... MyEvent- = foo;}; MyEvent + = foo; ไม่ทำงาน ... )
TDaver

Resharper 6.1 จะไม่บ่นถ้าคุณประกาศว่าเป็นอาร์เรย์ ดูเหมือนว่าแปลกเล็กน้อย แต่ฉันจะไว้วางใจเครื่องมือของฉันในรายการนี้: MyEventHandler [] foo = {null}; foo [0] = ... {... MyEvent - = foo [0]; }; MyEvent + = foo [0];
Mike Post

21

จากหน่วยความจำข้อมูลจำเพาะไม่ได้รับประกันพฤติกรรมอย่างใดอย่างหนึ่งเมื่อเทียบกับผู้รับมอบสิทธิ์ที่สร้างด้วยวิธีการที่ไม่ระบุชื่อ

หากคุณต้องการยกเลิกการเป็นสมาชิกคุณควรใช้วิธี "ปกติ" หรือเก็บผู้รับมอบสิทธิ์ไว้ที่อื่นเพื่อที่คุณจะสามารถยกเลิกการสมัครกับผู้รับมอบสิทธิ์รายเดิมที่คุณเคยสมัคร


ฉันจอนคุณหมายถึงอะไร ฉันไม่เข้าใจ โซลูชันที่เปิดเผยโดย "J c" ทำงานไม่ถูกต้องหรือไม่
Eric Ouellet

@EricOuellet: คำตอบนั้นโดยทั่วไปแล้วคือการดำเนินการ "รักษาผู้รับมอบสิทธิ์ไว้ที่อื่นเพื่อให้คุณสามารถยกเลิกการสมัครกับผู้รับมอบสิทธิ์เดิมที่คุณเคยสมัคร"
Jon Skeet

จอนฉันขอโทษที่ฉันอ่านคำตอบของคุณหลายครั้งพยายามคิดว่าคุณหมายถึงอะไรและที่ "J c" ไม่ได้ใช้ตัวแทนเดียวกันในการสมัครและยกเลิกการสมัคร แต่ฉันไม่สามารถรู้ได้ บางทีคุณอาจบอกฉันถึงบทความที่อธิบายสิ่งที่คุณพูด ฉันรู้เกี่ยวกับชื่อเสียงของคุณและฉันอยากจะเข้าใจสิ่งที่คุณหมายถึงอะไรที่คุณสามารถเชื่อมโยงไปยังจะขอบคุณจริงๆ
Eric Ouellet

1
ฉันพบ: msdn.microsoft.com/en-us/library/ms366768.aspxแต่พวกเขาไม่แนะนำให้ใช้โดยไม่ระบุชื่อ แต่พวกเขาไม่ได้บอกว่ามีปัญหาที่สำคัญหรือไม่?
Eric Ouellet

ฉันพบมัน ... ขอบคุณมาก (ดูคำตอบ Michael Blome): social.msdn.microsoft.com/Forums/en-US/csharplanguage/thread/…
Eric Ouellet

16

ใน 3.0 สามารถย่อให้เป็น:

MyHandler myDelegate = ()=>Console.WriteLine("I did it!");
MyEvent += myDelegate;
...
MyEvent -= myDelegate;

15

เนื่องจากคุณสมบัติฟังก์ชั่นท้องถิ่น C # 7.0ได้รับการเผยแพร่วิธีการแนะนำโดยเจคจะกลายเป็นอย่างจริงๆ

void foo(object s, MyEventArgs ev)
{
    Console.WriteLine("I did it!");
    MyEvent -= foo;
};
MyEvent += foo;

ดังนั้นโดยสุจริตคุณไม่มีฟังก์ชั่นที่ไม่ระบุชื่อเป็นตัวแปรที่นี่ แต่ฉันคิดว่าแรงจูงใจที่จะใช้ในกรณีของคุณสามารถนำไปใช้กับฟังก์ชั่นท้องถิ่น


1
เพื่อทำให้การอ่านง่ายยิ่งขึ้นคุณสามารถย้าย MyEvent + = foo; บรรทัดที่จะอยู่ก่อนการประกาศของ foo
Mark Zhukovsky

9

แทนที่จะเก็บการอ้างอิงไปยังผู้รับมอบสิทธิ์ใด ๆ คุณสามารถใช้คลาสของคุณเพื่อให้รายการการเรียกคืนของกิจกรรมกลับไปยังผู้โทร โดยทั่วไปคุณสามารถเขียนสิ่งนี้ (สมมติว่า MyEvent ถูกประกาศใน MyClass):

public class MyClass 
{
  public event EventHandler MyEvent;

  public IEnumerable<EventHandler> GetMyEventHandlers()  
  {  
      return from d in MyEvent.GetInvocationList()  
             select (EventHandler)d;  
  }  
}

ดังนั้นคุณสามารถเข้าถึงรายการการร้องขอทั้งหมดได้จากนอก MyClass และยกเลิกการเป็นผู้จัดการที่คุณต้องการ ตัวอย่างเช่น

myClass.MyEvent -= myClass.GetMyEventHandlers().Last();

ผมเคยเขียนโพสต์เต็มรูปแบบเกี่ยวกับ tecnique นี้ที่นี่


2
นี่หมายความว่าฉันสามารถยกเลิกการสมัครรับอินสแตนซ์ต่าง ๆ (เช่นไม่ใช่ฉัน) โดยบังเอิญได้หากพวกเขาสมัครรับข้อมูลหลังจากฉัน
dumbledad

@dumbledad ของหลักสูตรนี้จะ alaways ลงทะเบียนลงทะเบียนล่าสุด หากคุณต้องการที่จะยกเลิกการเป็นตัวแทนที่ไม่ระบุชื่อแบบไดนามิกคุณจะต้องระบุมัน ฉันขอแนะนำให้เก็บการอ้างอิงแล้ว :)
LuckyLikey

มันเจ๋งมากคุณกำลังทำอะไรอยู่ แต่ฉันไม่สามารถจินตนาการได้ว่ากรณีใดกรณีนี้ที่จะเป็นประโยชน์ แต่ฉันไม่ได้แก้คำถามของ OP -> +1 IMHO หนึ่งไม่ควรใช้ผู้รับมอบสิทธิ์ที่ไม่ระบุชื่อหากพวกเขาควรจะลงทะเบียนในภายหลัง การทำให้พวกเขาโง่ -> ใช้วิธีที่ดีกว่า การลบผู้รับมอบสิทธิ์บางรายในรายการการเรียกใช้นั้นค่อนข้างสุ่มและไร้ประโยชน์ ช่วยแก้ให้ด้วยนะถ้าฉันผิด. :)
LuckyLikey

6

ชนิดของวิธีการที่อ่อนแอ:

public class SomeClass
{
  private readonly IList<Action> _eventList = new List<Action>();

  ...

  public event Action OnDoSomething
  {
    add {
      _eventList.Add(value);
    }
    remove {
      _eventList.Remove(value);
    }
  }
}
  1. แทนที่เมธอดการเพิ่ม / ลบเหตุการณ์
  2. เก็บรายการตัวจัดการเหตุการณ์เหล่านั้น
  3. เมื่อจำเป็นให้ล้างข้อมูลทั้งหมดและเพิ่มผู้อื่น

สิ่งนี้อาจไม่ทำงานหรือเป็นวิธีที่มีประสิทธิภาพที่สุด แต่ควรทำให้งานเสร็จ


14
ถ้าคุณคิดว่ามันเป็นคนง่อยอย่าโพสต์เลย
Jerry Nixon

2

หากคุณต้องการควบคุมการยกเลิกการสมัครคุณต้องไปตามเส้นทางที่ระบุไว้ในคำตอบที่คุณยอมรับ อย่างไรก็ตามหากคุณกังวลเกี่ยวกับการล้างข้อมูลอ้างอิงเมื่อคลาสการสมัครของคุณไม่อยู่ในขอบเขตก็จะมีโซลูชันอื่น (ซับซ้อนเล็กน้อย) ซึ่งเกี่ยวข้องกับการใช้การอ้างอิงที่อ่อนแอ ฉันเพิ่งโพสต์คำถามและคำตอบในหัวข้อนี้


2

ทางออกเดียวที่ง่าย:

เพียงส่งตัวแปร eventhandle เป็นพารามิเตอร์ให้กับตัวเอง เหตุการณ์หากคุณมีกรณีที่คุณไม่สามารถเข้าถึงตัวแปรที่สร้างขึ้นดั้งเดิมเนื่องจากการใช้มัลติเธรดคุณสามารถใช้สิ่งนี้:

MyEventHandler foo = null;
foo = (s, ev, mehi) => MyMethod(s, ev, foo);
MyEvent += foo;

void MyMethod(object s, MyEventArgs ev, MyEventHandler myEventHandlerInstance)
{
    MyEvent -= myEventHandlerInstance;
    Console.WriteLine("I did it!");
}

เกิดอะไรขึ้นถ้า MyEvent ถูกเรียกสองครั้งก่อนที่จะMyEvent -= myEventHandlerInstance;ถูกเรียกใช้ หากเป็นไปได้แสดงว่าคุณมีข้อผิดพลาด แต่ฉันไม่แน่ใจว่าเป็นอย่างนั้นหรือเปล่า
LuckyLikey

0

หากคุณต้องการอ้างถึงวัตถุบางอย่างที่มีผู้รับมอบสิทธิ์นี้คุณอาจใช้ Delegate.CreateDelegate (ประเภทเป้าหมายวัตถุ MethodInfo methodInfo) .net พิจารณาผู้รับมอบสิทธิ์เท่ากับเป้าหมายและ methodInfo


0

หากวิธีที่ดีที่สุดคือการอ้างอิงเกี่ยวกับ eventHandler ที่สมัครสมาชิกสิ่งนี้สามารถทำได้โดยใช้พจนานุกรม

ในตัวอย่างนี้ฉันต้องใช้วิธีที่ไม่ระบุชื่อเพื่อรวมพารามิเตอร์ mergeColumn สำหรับชุด DataGridViews

การใช้เมธอด MergeColumn พร้อมพารามิเตอร์เปิดใช้งานที่ตั้งค่าเป็นจริงเปิดใช้งานเหตุการณ์ในขณะที่ใช้มันด้วยการปิดใช้งานที่ผิดพลาด

static Dictionary<DataGridView, PaintEventHandler> subscriptions = new Dictionary<DataGridView, PaintEventHandler>();

public static void MergeColumns(this DataGridView dg, bool enable, params ColumnGroup[] mergedColumns) {

    if(enable) {
        subscriptions[dg] = (s, e) => Dg_Paint(s, e, mergedColumns);
        dg.Paint += subscriptions[dg];
    }
    else {
        if(subscriptions.ContainsKey(dg)) {
            dg.Paint -= subscriptions[dg];
            subscriptions.Remove(dg);
        }
    }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.