Chapter96:Delegates

No Comments

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.

Section96.5:DelegateEquality

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 posts
No Comments

Recent Posts

Leave a Comment