談及到C#的基本特性,“委托”是不得不去了解和深入分析的一個特性。對於大多數剛入門的程序員談到“委托”時,都會想到“將方法作為方法的參數進行傳遞”,很多時候都只是知道簡單的定義,主要是因為“委托”在理解上有較其他特性比較難的地方。在本次說明中,不會將委托的簡單聲明和調用作為重點。
“委托”不需要直接定義一個要執行的行為,而是將這個行為用某種方法“包含”在一個對象中。這個對象可以像其他任何對象那樣使用。在該對象中,可以執行封裝的操作。可以選擇將委托看作之定義了一個方法的接口,將委托的實例看作實現了那個接口的對象。
在“委托”的相關定義中,我們可以不難看出,“委托與方法“相比較於“接口與類”有著設計理念上的相似部分,產生的背景源於”設計原則“中的”開放-封閉原則“,”開放-封閉“原則:是說軟件實體(類,模塊,函數等等)應該可以擴展,但是不可修改。換一種說法可能更好的理解”對於擴展是開放的,對於更改是封閉的“,面對新的需求,對於程序的改動是通過增加新的代碼進行的,而不是更改現有的代碼。
在C#中委托用delegate關鍵字定義,使用new操作符構造委托實例,采用傳統的方法調用語法來回調函數(只是要用引用了委托對象的一個變量代替方法名)。在C#中,委托在編譯的時候會被編譯成類。對於委托的一個說明:委托是一個類,它定義了方法的類型,使得可以將方法當作另一個方法的參數來進行傳遞。委托類既可嵌套在一個類型中定義,也可以在全局范圍內定義。由於委托是類,凡是可以定義類的地方,都可以定義委托。
接下來我們來看一下”委托“的組成,需要滿足的條件:
1.聲明委托類型。
2.必須有一個方法包含了要執行的代碼。
3.必須創建一個委托實例。
4.必須調用委托實例。
接下來大致的了解一下上面所提出的4項條件:
委托類型實際上只是參數類型的一個列表以及返回類型。規定了類型的實例能表示的操作。在調用一個委托實例的時候,必須保證使用的參數完全匹配,而且能以指定的方式使用返回值。對於委托實例的創建,取決於操作使用實例方法還是靜態方法(如果操作是靜態方法,指定類型名稱就可以,如果是操作實例方法,需要先創建類型的實例)。對於委托的調用,可以直接調用委托的實例的方法就可以完成對應的操作。
以上談及了”委托“的定義和組成,接下來我們來了解一下如何將方法綁定到”委托“上,以及委托的合並和刪除。
可以將多個方法賦給同一個委托,委托實例實際有一個操作列表與之關聯。在System.Delegate類型中提供了兩個靜態方法Combine()和Remove()負責委托實例的新增和刪除操作。但是在我們的實際開發中,較多的采用-=和+=操作符。
在FCL中,所有的委托類型都派生自MulticastDelegate,該類型在System.MulticastDelegate類型中。
具體來看一下Combine()方法的底層實現代碼:
[System.Runtime.InteropServices.ComVisible(true)]
public static Delegate Combine(params Delegate[] delegates)
{
if (delegates == null || delegates.Length == 0)
return null;
Delegate d = delegates[0];
for (int i = 1; i < delegates.Length; i++)
d = Combine(d,delegates[i]);
return d;
}
public static Delegate Combine(Delegate a, Delegate b)
{
if ((Object)a == null)
return b;
return a.CombineImpl(b);
}
以上兩個方法為System.Delegate類型中,CombineImpl方法在MulticastDelegate重寫。
[System.Security.SecuritySafeCritical]
protected override sealed Delegate CombineImpl(Delegate follow)
{
if ((Object)follow == null)
return this;
if (!InternalEqualTypes(this, follow))
throw new ArgumentException(Environment.GetResourceString("Arg_DlgtTypeMis"));
MulticastDelegate dFollow = (MulticastDelegate)follow;
Object[] resultList;
int followCount = 1;
Object[] followList = dFollow._invocationList as Object[];
if (followList != null)
followCount = (int)dFollow._invocationCount;
int resultCount;
Object[] invocationList = _invocationList as Object[];
if (invocationList == null)
{
resultCount = 1 + followCount;
resultList = new Object[resultCount];
resultList[0] = this;
if (followList == null)
{
resultList[1] = dFollow;
}
else
{
for (int i = 0; i < followCount; i++)
resultList[1 + i] = followList[i];
}
return NewMulticastDelegate(resultList, resultCount);
}
else
{
int invocationCount = (int)_invocationCount;
resultCount = invocationCount + followCount;
resultList = null;
if (resultCount <= invocationList.Length)
{
resultList = invocationList;
if (followList == null)
{
if (!TrySetSlot(resultList, invocationCount, dFollow))
resultList = null;
}
else
{
for (int i = 0; i < followCount; i++)
{
if (!TrySetSlot(resultList, invocationCount + i, followList[i]))
{
resultList = null;
break;
}
}
}
}
if (resultList == null)
{
int allocCount = invocationList.Length;
while (allocCount < resultCount)
allocCount *= 2;
resultList = new Object[allocCount];
for (int i = 0; i < invocationCount; i++)
resultList[i] = invocationList[i];
if (followList == null)
{
resultList[invocationCount] = dFollow;
}
else
{
for (int i = 0; i < followCount; i++)
resultList[invocationCount + i] = followList[i];
}
}
return NewMulticastDelegate(resultList, resultCount, true);
}
}
再來具體看一下Remove()方法的底層實現代碼,RemoveAll和Remove兩個方法為System.Delegate類型中,CombineImpl方法在MulticastDelegate重寫。:
public static Delegate RemoveAll(Delegate source, Delegate value)
{
Delegate newDelegate = null;
do
{
newDelegate = source;
source = Remove(source, value);
}
while (newDelegate != source);
return newDelegate;
}
[System.Security.SecuritySafeCritical]
public static Delegate Remove(Delegate source, Delegate value)
{
if (source == null)
return null;
if (value == null)
return source;
if (!InternalEqualTypes(source, value))
throw new ArgumentException(Environment.GetResourceString("Arg_DlgtTypeMis"));
return source.RemoveImpl(value);
}
[System.Security.SecuritySafeCritical]
protected override sealed Delegate RemoveImpl(Delegate value)
{ MulticastDelegate v = value as MulticastDelegate;
if (v == null)
return this;
if (v._invocationList as Object[] == null)
{
Object[] invocationList = _invocationList as Object[];
if (invocationList == null)
{
if (this.Equals(value))
return null;
}
else
{
int invocationCount = (int)_invocationCount;
for (int i = invocationCount; --i >= 0; )
{
if (value.Equals(invocationList[i]))
{
if (invocationCount == 2)
{
return (Delegate)invocationList[1-i];
}
else
{
Object[] list = DeleteFromInvocationList(invocationList, invocationCount, i, 1);
return NewMulticastDelegate(list, invocationCount-1, true);
}
}
}
}
}
else
{
Object[] invocationList = _invocationList as Object[];
if (invocationList != null) {
int invocationCount = (int)_invocationCount;
int vInvocationCount = (int)v._invocationCount;
for (int i = invocationCount - vInvocationCount; i >= 0; i--)
{
if (EqualInvocationLists(invocationList, v._invocationList as Object[], i, vInvocationCount))
{
if (invocationCount - vInvocationCount == 0)
{
return null;
}
else if (invocationCount - vInvocationCount == 1)
{
return (Delegate)invocationList[i != 0 ? 0 : invocationCount-1];
}
else
{
Object[] list = DeleteFromInvocationList(invocationList, invocationCount, i, vInvocationCount);
return NewMulticastDelegate(list, invocationCount - vInvocationCount, true);
}
}
}
}
}
return this;
}
在以上的代碼中,我們了解到了在.NET底層是如何實現委托實例的綁定和刪除綁定。
在調用委托實例時,所有的操作都是順序執行的。如果調用具有一個非void的返回類型,則調用的返回值是最後一個操作的返回值。如果調用列表中任何操作拋出異常,都會阻止執行後續的操作。
在上面提到了委托列表中出現非void實例調用,如果委托實例中出現多個非void調用,並且需要獲取所有的委托實例的返回值結果,那麼應該如何操作,在.NET紅提供了一個方法GetInvocationList(),用於獲取委托鏈表。
接下來具體了解一下GetInvocationList()的底層代碼:
[System.Security.SecuritySafeCritical]
public override sealed Delegate[] GetInvocationList()
{
Delegate[] del;
Object[] invocationList = _invocationList as Object[];
if (invocationList == null)
{
del = new Delegate[1];
del[0] = this;
}
else
{
int invocationCount = (int)_invocationCount;
del = new Delegate[invocationCount];
for (int i = 0; i < invocationCount; i++)
del[i] = (Delegate)invocationList[i];
}
return del;
}
當獲取到委托實例列表後,可采用循環迭代的方式,依次獲取每個委托實例的返回值。
再來了解一個屬性Method,具體看一下此屬性的底層實現代碼:
public MethodInfo Method
{
get
{
return GetMethodImpl();
}
}
[System.Security.SecuritySafeCritical]
protected virtual MethodInfo GetMethodImpl()
{
if ((_methodBase == null) || !(_methodBase is MethodInfo))
{
IRuntimeMethodInfo method = FindMethodHandle();
RuntimeType declaringType = RuntimeMethodHandle.GetDeclaringType(method);
if (RuntimeTypeHandle.IsGenericTypeDefinition(declaringType) || RuntimeTypeHandle.HasInstantiation(declaringType))
{
bool isStatic = (RuntimeMethodHandle.GetAttributes(method) & MethodAttributes.Static) != (MethodAttributes)0;
if (!isStatic)
{
if (_methodPtrAux == (IntPtr)0)
{
Type currentType = _target.GetType();
Type targetType = declaringType.GetGenericTypeDefinition();
while (currentType != null)
{
if (currentType.IsGenericType &&
currentType.GetGenericTypeDefinition() == targetType)
{
declaringType = currentType as RuntimeType;
break;
}
currentType = currentType.BaseType;
}
BCLDebug.Assert(currentType != null || _target.GetType().IsCOMObject, "The class hierarchy should declare the method");
}
else
{
MethodInfo invoke = this.GetType().GetMethod("Invoke");
declaringType = (RuntimeType)invoke.GetParameters()[0].ParameterType;
}
}
}
_methodBase = (MethodInfo)RuntimeType.GetMethodBase(declaringType, method);
}
return (MethodInfo)_methodBase;
}
以上是System.Delegate類中的定義,接下來看一下MulticastDelegate重寫:
[System.Security.SecuritySafeCritical]
protected override MethodInfo GetMethodImpl()
{
if (_invocationCount != (IntPtr)0 && _invocationList != null)
{
Object[] invocationList = _invocationList as Object[];
if (invocationList != null)
{
int index = (int)_invocationCount - 1;
return ((Delegate)invocationList[index]).Method;
}
MulticastDelegate innerDelegate = _invocationList as MulticastDelegate;
if (innerDelegate != null)
{
return innerDelegate.GetMethodImpl();
}
}
else if (IsUnmanagedFunctionPtr())
{
if ((_methodBase == null) || !(_methodBase is MethodInfo))
{
IRuntimeMethodInfo method = FindMethodHandle();
RuntimeType declaringType = RuntimeMethodHandle.GetDeclaringType(method);
if (RuntimeTypeHandle.IsGenericTypeDefinition(declaringType) || RuntimeTypeHandle.HasInstantiation(declaringType))
{
RuntimeType reflectedType = GetType() as RuntimeType;
declaringType = reflectedType;
}
_methodBase = (MethodInfo)RuntimeType.GetMethodBase(declaringType, method);
}
return (MethodInfo)_methodBase;
}
return base.GetMethodImpl();
}
以上是對委托的相關定義,以及有關委托的一些操作方法的說明,沒有具體指出如何去創建和使用委托,因為委托的簡單創建和一般應用,對於大部分開發者來說是相對較為簡單的,因為微軟在不斷的對C#的語法進行提升和修改,極大的簡化了對應的操作。但是正是由於在應用層做了較大的封裝,這也會導致特性在底層的復雜度慢慢的增大。