Section96.1:Declaringadelegatetype
ThefollowingsyntaxcreatesadelegatetypewithnameNumberInOutDelegate,representingamethodwhichtakes an intand returns an int.
publicdelegateintNumberInOutDelegate(intinput);
Thiscanbeusedasfollows:
publicstaticclassProgram
{
staticvoidMain()
{
NumberInOutDelegatesquare=MathDelegates.Square;
intanswer1=square(4);
Console.WriteLine(answer1);//Willoutput16
NumberInOutDelegatecube=MathDelegates.Cube;
intanswer2=cube(4); Console.WriteLine(answer2);//Willoutput64
}
}
publicstaticclassMathDelegates
{
staticintSquare(intx)
{
returnx*x;
}
staticintCube(intx)
{
returnx*x*x;
}
}
TheexampledelegateinstanceisexecutedinthesamewayastheSquaremethod.Adelegateinstanceliterallyacts asadelegateforthecaller:thecallerinvokesthedelegate,andthenthedelegatecallsthetargetmethod.This indirection decouples the caller from the target method.
You can declare a generic delegate type, and in that case you may specify that the type is covariant (out) orcontravariant(in)insomeofthetypearguments.Forexample:
publicdelegateTToConverter<inTFrom,outTTo>(TFrominput);
Likeothergenerictypes,genericdelegatetypescanhaveconstraints,suchaswhereTFrom:struct,IConvertiblewhereTTo:new().
Avoid co- and contravariance for delegate types that are meant to be used for multicast delegates, such as event handler types. This is because concatenation (+) can fail if the run-time type is different from the compile-time type because of the variance. For example, avoid:
publicdelegatevoidEventHandler<inTEventArgs>(objectsender,TEventArgse);
Instead,useaninvariantgenerictype:
publicdelegatevoidEventHandler<TEventArgs>(objectsender,TEventArgse);
Alsosupportedaredelegateswheresomeparametersaremodifiedbyreforout,asin:
publicdelegateboolTryParser<T>(stringinput,outTresult);
(sample use TryParser<decimal>example=decimal.TryParse;), or delegates where the last parameter has the paramsmodifier.Delegatetypescanhaveoptionalparameters(supplydefaultvalues).Delegatetypescanuse pointertypeslikeint*orchar*intheirsignaturesorreturntypes(useunsafekeyword).Adelegatetypeandits parameters can carry custom attributes.
Section 96.2: The Func<T, TResult>, Action<T>and Predicate<T>delegatetypes
TheSystemnamespacecontainsFunc<…,TResult>delegatetypeswithbetween0and15genericparameters, returning type TResult.
privatevoidUseFunc(Func<string>func)
{
stringoutput=func();//Funcwithasinglegenerictypeparameterreturnsthattype
Console.WriteLine(output);
}
privatevoidUseFunc(Func<int,int,string>func)
{
stringoutput=func(4,2);//Funcwithmultiplegenerictypeparameterstakesallbutthe firstasparametersofthattype
Console.WriteLine(output);
}
TheSystemnamespacealsocontainsAction<…>delegatetypeswithdifferentnumberofgenericparameters (from0to16).ItissimilartoFunc<T1,..,Tn>,butitalwaysreturnsvoid.
privatevoidUseAction(Actionaction)
{
action();//Thenon-genericActionhasnoparameters
}
privatevoidUseAction(Action<int,string>action)
{
action(4,”two”);//Thegenericactionisinvokedwithparametersmatchingitstypearguments
}
Predicate<T>isalsoaformofFuncbutitwillalwaysreturnbool.Apredicateisawayofspecifyingacustom criteria.Dependingonthevalueoftheinputandthelogicdefinedwithinthepredicate,itwillreturneithertrueor false.Predicate<T>thereforebehavesinthesamewayasFunc<T,bool>andbothcanbeinitializedandusedin the same way.
Predicate<string>predicate=s=>s.StartsWith(“a”);Func<string,bool>func
=s=>s.StartsWith(“a”);
//Bothofthesereturntrue
varpredicateReturnsTrue=predicate(“abc”);
varfuncReturnsTrue=func(“abc”);
//Bothofthesereturnfalse
varpredicateReturnsFalse=predicate(“xyz”);
varfuncReturnsFalse=func(“xyz”);
The choice of whether to use Predicate<T>or Func<T,bool>is really a matter of opinion. Predicate<T>is arguablymoreexpressiveoftheauthor’sintent,whileFunc<T,bool>islikelytobefamiliartoagreaterproportion of C# developers.
Inadditiontothat,therearesomecaseswhereonlyoneoftheoptionsisavailable,especiallywheninteractingwith another API. For example List<T>and Array<T>generally take Predicate<T>for their methods, while most LINQ extensions only accept Func<T,bool>.
Section96.3:CombineDelegates(MulticastDelegates)
Addition+andsubtraction-operationscanbeusedtocombinedelegateinstances.Thedelegatecontainsalistof the assigned delegates.
usingSystem;
usingSystem.Reflection;usingSyst
em.Reflection.Emit;
namespaceDelegatesExample{
classMainClass{
privatedelegatevoidMyDelegate(inta);
privatestaticvoidPrintInt(inta){
Console.WriteLine(a);
}
privatestaticvoidPrintType<T>(Ta){
Console.WriteLine(a.GetType());
}
publicstaticvoidMain(string[]args)
{
MyDelegated1=PrintInt;
MyDelegated2=PrintType;
//Output:
//1
d1(1);
//Output:
//System.Int32
d2(1);
MyDelegated3=d1+d2;
//Output:
//1
//System.Int32
d3(1);
MyDelegated4=d3-d2;
//Output:
//1
d4(1);
//Output:
//True
Console.WriteLine(d1==d4);
}
}
}
Inthis example d3isa combination ofd1andd2delegates, so whencalled the programoutputs both1and
System.Int32strings.
Combiningdelegateswithnonvoidreturntypes:
If a multicast delegate has a nonvoidreturn type, the caller receives the return value from the last method to be invoked. The preceding methods are still called, but their return values are discarded.
classProgram
{
publicdelegateintTransformer(intx);
staticvoidMain(string[]args)
{
Transformert=Square;
t+=Cube;
Console.WriteLine(t(2)); //O/P8
}
staticintSquare(intx){returnx*x;}
staticintCube(intx){returnx*x*x;}
}
t(2)willcallfirstSquareandthenCube.ThereturnvalueofSquareisdiscardedandreturnvalueofthelastmethod
i.e.Cubeisretained.
Section96.4:Safeinvokemulticastdelegate
Ever wanted to call a multicast delegate but you want the entire invokation list to be called even if an exception occurs in any in the chain. Then you are in luck, I have created an extension method that does just that, throwing an AggregateExceptiononlyafterexecutionoftheentirelistcompletes:
publicstaticclassDelegateExtensions
{
publicstaticvoidSafeInvoke(thisDelegatedel,paramsobject[]args)
{
varexceptions=newList<Exception>();
foreach(varhandlerindel.GetInvocationList())
{
try
{
handler.Method.Invoke(handler.Target,args);
}
catch(Exceptionex)
{
exceptions.Add(ex);
}
}
if(exceptions.Any())
{
thrownewAggregateException(exceptions);
}
}
}
publicclassTest
{
publicdelegatevoidSampleDelegate();
publicvoidRun()
{
SampleDelegate delegateInstance = this.Target2;
delegateInstance+=this.Target1;
try
{
delegateInstance.SafeInvoke();
}
catch(AggregateExceptionex)
{
//Doanyexceptionhandlinghere
}
}
privatevoidTarget1()
{
Console.WriteLine(“Target1executed”);
}
privatevoidTarget2()
{
Console.WriteLine(“Target2executed”);
thrownewException();
}
}
Thisoutputs:
Target2executed
Target1executed
Invokingdirectly,withoutSaveInvoke,wouldonlyexecuteTarget2.
Calling.Equals()onadelegatecomparesbyreferenceequality:
Action action1=()=>Console.WriteLine(“Hello delegates”); Action
action2=()=>Console.WriteLine(“Hello delegates”);
Actionaction1Again=action1;
Console.WriteLine(action1.Equals(action1))//True
Console.WriteLine(action1.Equals(action2))//False
Console.WriteLine(action1Again.Equals(action1))//True
These rules also apply when doing +=or -=on a multicast delegate, for example when subscribing and unsubscribing from events.
Section96.6:Underlyingreferencesofnamedmethod
delegates
When assigning named methods to delegates, they will refer to the same underlying object if:
- They are the same instance method, on the same instance of a class
- They are the same static method on a class
publicclassGreeter
{
publicvoidWriteInstance()
{
Console.WriteLine(“Instance”);
}
publicstaticvoidWriteStatic()
{
Console.WriteLine(“Static”);
}
}
//…
Greetergreeter1=newGreeter();
Greetergreeter2=newGreeter();
Actioninstance1=greeter1.WriteInstance;Actioninstance2
=greeter2.WriteInstance;Actioninstance1Again=greeter1.
WriteInstance;
Console.WriteLine(instance1.Equals(instance2));//False
Console.WriteLine(instance1.Equals(instance1Again));//True
Action@static=Greeter.WriteStatic;
ActionstaticAgain=Greeter.WriteStatic;
Console.WriteLine(@static.Equals(staticAgain));//True
Section96.7:Assigninganamedmethodtoadelegate
Namedmethodscanbeassignedtodelegateswithmatchingsignatures:
publicstaticclassExample
{
publicstaticintAddOne(intinput)
{
returninput+1;
}
}
Func<int,int>addOne=Example.AddOne
Example.AddOnetakesanintandreturnsanint,itssignaturematchesthedelegateFunc<int,int>. Example.AddOnecanbedirectlyassignedtoaddOnebecausetheyhavematchingsignatures.
Section96.8:Assigningtoadelegatebylambda
Lambdascanbeusedtocreateanonymousmethodstoassigntoadelegate:
Func<int,int>addOne=x=>x+1;
Notethattheexplicitdeclarationoftypeisrequiredwhencreatingavariablethisway:
varaddOne=x=>x+1;//Doesnotwork
Section96.9:Encapsulatingtransformationsinfuncs
publicclassMyObject{
publicDateTime?TestDate{get;set;}
publicFunc<MyObject,bool>DateIsValid=myObject=>myObject.TestDate.HasValue&&myObject.TestDate>DateTime.Now;
publicvoidDoSomething(){
//Wecandothis:
if(this.TestDate.HasValue&&this.TestDate>DateTime.Now){
CallAnotherMethod();
}
//orthis:
if(DateIsValid(this)){
CallAnotherMethod();
}
}
}
Inthespiritofcleancoding,encapsulatingchecksandtransformationsliketheoneaboveasaFunccanmakeyour codeeasiertoreadandunderstand.Whiletheaboveexampleisverysimple,whatifthereweremultipleDateTime propertieseachwiththeirowndifferingvalidationrulesandwewantedtocheckdifferentcombinations?Simple, one-lineFuncsthateachhaveestablishedreturnlogiccanbebothreadableandreducetheapparentcomplexityof yourcode.ConsiderthebelowFunccallsandimaginehowmuchmorecodewouldbeclutteringupthemethod:
publicvoidCheckForIntegrity(){
if(ShipDateIsValid(this)&&TestResultsHaveBeenIssued(this)&&!TestResultsFail(this)){SendPassingTestNotification();
}
}
Section96.10:Passingdelegatesasparameters
Delegatescanbeusedastypedfunctionpointers:
classFuncAsParameters
{
publicvoidRun()
{
DoSomething(ErrorHandler1); DoSomething(ErrorHandler2);
}
publicboolErrorHandler1(stringmessage)
{
Console.WriteLine(message); var
shouldWeContinue = … return
shouldWeContinue;
}
publicboolErrorHandler2(stringmessage)
{
//…Writemessageto file…
varshouldWeContinue= …
returnshouldWeContinue;
}
publicvoidDoSomething(Func<string,bool>errorHandler)
{
//Inhere,wedon’tcarewhathandlerwegotpassed!
…
if(…error…)
{
if(!errorHandler(“Someerroroccurred!”))
{
//Thehandlerdecidedwecan’tcontinue
return;
}
}
}
}
Section96.11:Closureinsideadelegate
Closures are inline anonymous methods that have the ability to use Parentmethod variables and other anonymous methods which are defined in the parent’s scope.
In essence, a closure is a block of code which can be executed at a later time, but which maintains the environment in which it was first created – i.e. it can still use the local variables etc of the method which createdit,evenafterthatmethodhasfinishedexecuting.–JonSkeet
delegateinttestDel();
staticvoidMain(string[]args)
{
intfoo=4;
testDelmyClosure=delegate()
{
returnfoo;
};
intbar=myClosure();
}
ExampletakenfromClosuresin.NET.
About us and this blog
We are a digital marketing company with a focus on helping our customers achieve great results across several key areas.
Request a free quote
We offer professional SEO services that help websites increase their organic search score drastically in order to compete for the highest rankings even when it comes to highly competitive keywords.
Subscribe to our newsletter!
More from our blog
See all postsRecent Posts
- Chapter110:Timers 10/05/2024
- Chapter109:Stream 09/05/2024
- Chapter108:CheckedandUnchecked 08/05/2024