PDA

View Full Version : آموزش delegate در VB.Net



بابک زواری
یک شنبه 12 تیر 1384, 14:30 عصر
خوندن این دو مقاله رو به دوستان حتما توصیه میکنم

http://abstractvb.com/code.asp?A=1084
http://www.dotnet247.com/247reference/msgs/58/292845.aspx

بابک زواری
یک شنبه 12 تیر 1384, 14:48 عصر
What the is a Delegate?
Microsoft gives this rather cryptic description:
"A delegate is a reference type that refers to a Shared method of a type or to an instance method of an object. The closest equivalent of a delegate in other languages is a function pointer, but whereas a function pointer can only reference Shared functions, a delegate can reference both Shared and instance methods."

So what does this mean in English? It means that a delegate is basically just a fancy function pointer like in C++. (you can also think of it like a callback) And a function pointer is just a reference to a function, it lets some other code call your function without needing to know where your function is actually located.



Why not use Events?
Lets try out a delegate so you can catch why it is so important.

What if you had a class represented a customer, and when your customer class is modified you wanted to let another block of code know that the class had been modified.

You could do this:

Public Class Customer
Public Event SomethingHappened(ByVal customerevent As String)

Private _address As String

Public Property Address() As String
Get
Return _address
End Get
Set(ByVal Value As String)
_address = Value

RaiseEvent SomethingHappened("Address Changed to:" & Value)
End Set
End Property
End Class

Then the object that implements your class could try to grab the event like this:

Public Class MyProgram
Private WithEvents c As New Customer()

Private Sub c_SomethingHappened(ByVal customerevent As String) Handles c.SomethingHappened
'handle event here
End Sub
End Class

This has a few major limitations:

The MyProgram class need to create and own an instance of the Customer class.
The Customer class needs to know who to give the message to. (it's parent or owner in this case.)
This example also has the limitation that only one instance of MyProgram would be notified. If you create a new MyProgram you would also get a new Customer class. Not very scaleable.

Enter The Delegate
Before I go too far I must admit something for the purists, we actually used delegates in the example above. All events in .NET actually use delegates in the background to wire up events. Events are really just a modified form of a delegate.

How can a delegate solve the issues above? Lets try the same example using delegates:

Public Class Customer
Public Delegate Sub AddressChanged(ByVal oldAddress As String, ByVal newAddress As String)
Private addrChanged As AddressChanged

Private _address As String

Public Sub NotifyOnAddressChange(ByVal value As AddressChanged)
addrChanged = value
End Sub

Public Property Address() As String
Get
Return _address
End Get
Set(ByVal Value As String)
addrChanged.Invoke(_address, Value)

_address = Value
End Set
End Property
End Class

Ok there is our new Customer class. Let's walk through it. The first line is the delegate signature. Basically this acts like a prototype in C++. It defines what the procedure this delegate will call looks like. In this case our procedure must accept two strings. The name of this procedure does not matter it can be called anything, but it must accept two strings like our signature shows.

Public Delegate Sub AddressChanged(ByVal oldAddress As String, ByVal newAddress As String)

Next we just declare a variable to hold the delegate. Notice the delegate is now a type we can use to create variables of, just like a string.

Private addrChanged As AddressChanged

Next up is our NotifyOnAddressChange procedure. This is used by the caller to pass us a delegate of the function the caller wants us to run. Notice we use our delegate again to specify the type of the parameter being passed. This is why the delegate was declared public earlier, so that the caller could create one and give it to us. We then just take the delegate and save it.

Public Sub NotifyOnAddressChange(ByVal value As AddressChanged)
addrChanged = value
End Sub

Our address property is where the action happens in this example. Once the caller has given us a delegate and the address is changed by anyone the Invoke method is called on the instance of the delegate that passes the old and new addresses to the caller.

Public Property Address() As String
Get
Return _address
End Get
Set(ByVal Value As String)
addrChanged.Invoke(_address, Value)

_address = Value
End Set
End Property

Ok I admit that this topic can be confusing so lets check out an example of the calling program:

Public Class MyProgram
Private c As New Customer("Kelly", "Elias", "13th Ave SE")

Private Sub StartHere()
c.NotifyOnAddressChange(New Customer.AddressChanged(AddressOf AddressModified))
End Sub

Public Sub AddressModified(ByVal oldAddress As String, ByVal newAddress As String)
MsgBox("Address " & oldAddress & " changed to " & newAddress)
End Sub
End Class

So when the StartHere subroutine runs it calls the NotifyOnAddressChange procedure passing it a new delegate matching the AddressModified Signature we created in our Customer class. This delegate is pointing at the AddressModified subroutine. (using the addressof method) This is all we need to do. Once the address changes the customer object will invoke the delegate and our AddressModified procedure will be called.

So your probably wondering how this fixes any of the problem above right? Well lets look at the first two:

The MyProgram class need to create and own an instance of the Customer class.
The Customer class needs to know who to give the message to. (it's parent or owner in this case.)
So we could also do this:

Public Class MyProgram
Private Sub StartHere(ByRef c As Customer)
c.NotifyOnAdressChange(New Customer.AddressChanged(AddressOf AddressModified))
End Sub

Public Sub AddressModified(ByVal oldAddress As String, ByVal newAddress As String)
MsgBox("Address " & oldAddress & " changed to " & newAddress)
End Sub
End Class

Notice now that our customer object is being passed to our StartHere procedure. We are no longer creating it, and do not own it, but the delegate will still work. The customer class has no clue that MyProgram is using it, and we are not the parent of the object.

The last one of our problems gets its own section.

This example also has the limitation that only one instance of MyProgram would be notified. If you create a new MyProgram you would also get a new Customer class. Not very scaleable.


Multicasting Delegates
Multicasting Delegates is a fancy way of saying that we want to have a single delegate point to multiple functions. Lets go back and look at our NotifyOnAddressChange procedure with a slight modification:

Public Sub NotifyOnAdressChange(ByVal value As AddressChanged)
If addrChanged Is Nothing Then
addrChanged = value
Else
addrChanged.Combine(addrChanged, value)
End If
End Sub

Notice the If...Else statement, it's new. We added the following line:

addrChanged.Combine(addrChanged, value)

This is what gives us the multicasting. This combine method lets us take two separate delegates and combine them into one. When we invoke the new combined delegate it will call both delegates. (two for the price of one, is always a good deal right?)


Conclusion
Delegates are everywhere in the .NET framework, and to get proficient with VB.NET or C# you'll need to understand them. At the last two TechEd conferences Microsoft stressed the importance of delegates, and you can be sure that they are going to be used more often sinec they are a great way to enable classes to communicate.

I hope this tutorial helps clear up the fog around delegates in the .NET framework

sm
دوشنبه 13 تیر 1384, 14:09 عصر
ممنون

reza6384
شنبه 04 آبان 1387, 19:21 عصر
سلام.

ببخشید. من خوب متوجه نشدم. یعنی توضیحاتش خیلی زیاده.
ممکنه یک نفر توضیح بده که چه موقع باید از delegate استفاده کنیم؟ و راجع به Address of و Invoke هم لطفا توضیح بده.

البته یه چیزی می دونم. این قضیه مربوط میشه به thread و اینکه جند تا thread بخوان به یه چیزی دسترسی داشته باشن. اگر میشه یک توضیح ساده بدین.

این هم مثال :



One nice feature of the SerialPort class is that you need not constantly
poll for incoming data. Instead, you just need to service the DataReceived
event and it will automatically fire when incoming data is detected.
However, as this event is running on a separate thread,
any attempt to update the main Form directly will result in an error.
Hence, you need to use a delegate to update controls on the main thread

'-------------------------------------------
' Event handler for the DataReceived
'-------------------------------------------
Private Sub DataReceived( _
ByVal sender As Object, _
ByVal e As System.IO.Ports.SerialDataReceivedEventArgs) _
Handles serialPort.DataReceived

txtDataReceived.Invoke(New _
myDelegate(AddressOf updateTextBox), _
New Object() {})
End Sub


'------------------------------------------------------
' Delegate and subroutine to update the Textbox control
'------------------------------------------------------
Public Delegate Sub myDelegate()
Public Sub updateTextBox()
With txtDataReceived
.Font = New Font("Garamond", 12.0!, FontStyle.Bold)
.SelectionColor = Color.Red
.AppendText(serialPort.ReadExisting)
.ScrollToCaret()
End With
End Sub

mostafaaa
شنبه 04 آبان 1387, 21:05 عصر
یکی از دوستان خوب سایت مبحث Delegate رو کاملا شرح دادن که میتونید توی لینک زیر ببینیدش. البته کدهاش C# ولی اگه کدها رو متوجه نشدین بگین تا کدهای Vb رو بزارم.
http://barnamenevis.org/forum/showthread.php?t=117839

reza6384
شنبه 04 آبان 1387, 22:02 عصر
خیلی ممنون مصطفی جان. نه مشکلی با کد #C ندارم.
من وقتی در سایت جستجو کردم فقط همین تاپیک رو پیدا کردم که دو تا لینک ذکر شده اول که یکیش وجود نداشت و دومی هم چیزی راجع به Delegate توش پیدا نکردم. در ضمن کاش توضیحات رو به جای تگ کد در یک Document قرار میدادن و اونرو Attach می کردن بهتر قابل استفاده بود.
در هر صورت مرسی.

reza6384
شنبه 04 آبان 1387, 23:53 عصر
مصطفی جان.
ممنون از اینکه لطف کردی ، لطفا به توضیحات زیر گوش کن :
من در برنامه ام یک DLL دارم با نام SMS_MANAGING که یک کلاس داره به نام SMS_MODERATOR.
این کلاس دو تا Property از نوع DataSet داره که هرکدوم جداولی دارن.

این کلاس یک Event داره به نام OutBox_Refreshed که می خوام وقتی اتفاق افتاد روتین زیر رو فراخوانی کنم.



Sub BindAndRefreshAllGrids()
Try
MsgBox("The caller of delegate is : " & Caller)
Me.DGV_IOB.DataSource = Me.SM.TempDS
Me.DGV_IOB.DataMember = Me.SM.TempDS.Tables(0).TableName
Me.DGV_IOB.Refresh()
Me.DGV_POB.DataSource = Me.SM.TempDS
Me.DGV_POB.DataMember = Me.SM.TempDS.Tables(1).TableName
Me.DGV_POB.Refresh()
Me.DGV_SNT.DataSource = Me.SM.PhoneMessages
Me.DGV_SNT.DataMember = "Sent"
Me.DGV_SNT.Refresh()
Catch ex As Exception
MsgBox(ex.Message & vbCr & ex.StackTrace, MsgBoxStyle.Exclamation, "Error")
EndTry
EndSub


آبجکتی که در فرمم از این کلاس Sms_Moderator تعریف کردم نامش SM هست.

و DGV_IOB و DGV_POB و DGV_SNT هم سه تا DataGridView هستند که روی فرم قرار دارن و برای نمایش اطلاعات جداول مذکور ازشون استفاده میکنم.

کدی که در ابتدا در قسمت Event Handler نوشته بودم این بود :



PrivateSub SM_OutboxRefreshed() Handles SM.OutboxRefreshed
Me.BindAndRefreshAllGrids()
EndSub


و خوب مشکلی که وجود داشت این بود که بعد از دومین بار رخ دادن رویداد Outbox_Refreshed خطای زیر نمایان میشد (تصویر ضمیمه)


با توجه به توضیحاتی که در لینکی که گفتی نوشته شده بود، اومدم یک Delegate به صورت زیر تعریف کردم :



PrivateDelegateSub DelegateForGrids()


و در قسمت GLOBAL اونرو New کردم و آدرس BindAndRefreshAllGrids رو بهش دادم :



Private DLG AsNew DelegateForGrids(AddressOf BindAndRefreshAllGrids)


و بعد کد Event Handler مربوط به SM_outbox_Refreshed رو به صورت زیر تغییر دادم :



PrivateSub SM_OutboxRefreshed() Handles SM.OutboxRefreshed
DLG()
EndSub


ولی مشکلم هنوز حل نشده. یعنی بازهم دومین باری که OutBox_Refresh رخ میده، این Erro که عکسش رو قرار دادم نمایان میشه.

شما لطف کن بگو کجا رو اشتباه میکنم.

ممنون.:بوس::بوس::بوس:

mostafaaa
یک شنبه 05 آبان 1387, 09:07 صبح
سلام آقا رضا.
این ارور مربوط به این میشه که شما از داخل یه Thread بخوای به اشیایی که توی یه Thread دیگه ساخته شده دسترسی پیدا کنی که طبیعتا و به صورت پیشفرض امکانپذیر نیست .
در ضمن توی توضیحاتت حرفی از Thread نزدی . لطفا اگه امکان داره کد هات روکامل بزار تا بررسیش کنیم.
یا شاید هم از BackgroundWorker استفاده کردی.

odiseh
یک شنبه 05 آبان 1387, 12:23 عصر
آقای بابک زواری
سلام و تشکر
من یه سوال در مورد کدی که گذاشتید داشتم:
کجای کدتون به اون Delegate ای که تعریف اش کردین، متد اضافه کردین؟

Dariuosh
یک شنبه 05 آبان 1387, 16:07 عصر
دوست داشتي اينم (http://barnamenevis.org/forum/showthread.php?t=108704) ببين ، شايد به کارت بياد

reza6384
یک شنبه 05 آبان 1387, 20:13 عصر
سلام آقا رضا.
این ارور مربوط به این میشه که شما از داخل یه Thread بخوای به اشیایی که توی یه Thread دیگه ساخته شده دسترسی پیدا کنی که طبیعتا و به صورت پیشفرض امکانپذیر نیست .
در ضمن توی توضیحاتت حرفی از Thread نزدی . لطفا اگه امکان داره کد هات روکامل بزار تا بررسیش کنیم.
یا شاید هم از BackgroundWorker استفاده کردی.

سلام مصطفی جان. یه متغیر Global از نوع String ساختم که ببینم که کی Delegate رو Invoke میکنه. دومین باری که Event اه Outbox_Refreshed فراخوانی میشه این اتفاق میفته و این Error رو میده. چجوی باید درستش کنم؟

mostafaaa
یک شنبه 05 آبان 1387, 21:00 عصر
سلام
اول یه سوال اینکه اون Dll رو خودت نوشتی ؟ چون گویا اون Event که اسمش OutBox_Refreshed از داخل یه Thread دیگه فراخوانی میشه و اگه Dll خودت نوشتی میشه مشکل رو از اونجا برطرف کرد. ولی اگه نه .
Delegate رو به این شکل instance کن

Private DLG As New DelegateForGrids(AddressOf SM_OutboxRefreshed)و اینجوری هم Invoke بکنش

Private Sub SM_OutboxRefreshed() Handles SM.OutboxRefreshed
If Me.DGV_IOB.InvokeRequired Then
Invoke(Del)
Else
Me.BindAndRefreshAllGrids()
End If
End Sub

reza6384
دوشنبه 06 آبان 1387, 11:44 صبح
سلام.
بله DLL رو خودم نوشتم. حالا این کدی رو که اینجا گذاشتی چک میکنم و نتیجه اش رو میگم.

ممنون.

reza6384
سه شنبه 07 آبان 1387, 12:50 عصر
مصطفی جان ممنون. مشکلم حل شد.
حالا میشه یه توضیح راجع به این Invoke و ارتباط Delegate با Event ها بدی؟