Code Focused
C# Goes Back to BASICs with Local Functions
C# finally gets the BASIC GOSUB feature through local functions, but local functions take that GOSUB-like capability up a few notches.
If you're an old-timer like me, you probably remember the original implementations of the BASIC language, including the coveted GOSUB statement. This feature allowed you to temporarily jump to a block of code, do some processing, and return to the point just after the initial jump:
100 PRINT "Enter Your First Name: ";
110 GOSUB 400
120 FIRSTNAME$ = RESPONSE$
200 PRINT "Enter Your Last Name: ";
210 GOSUB 400
220 LASTNAME$ = RESPONSE$
300 PRINT "Hello, "; FIRSTNAME$; " "; LASTNAME$
310 END
400 LINE INPUT RESPONSE$
410 IF RESPONSE$ = "" THEN END
420 RETURN
GOSUB was nice because it allowed you to reuse a chunk of logic without the need to retype that code repeatedly. In more advanced BASIC implementations, named functions could do the same thing. But even in such procedures, having a subordinate, nearby, repeatable block of code was both quick to enter and easy to use.
Visual Basic lost its own GoSub keyword in the move to the .NET Framework, but something similar is now included in C# 7. The feature is called local functions. In truth, these nested functions aren’t like the old GOSUB statement -- they're better! Whereas the old BASIC feature provided little more than a jump destination in its syntax, local functions in C# are full methods, complete with parameters, return values and, if you want, asynchronous execution. The code in Listing 1 uses a local function to append a comma to a string only when needed.
Listing 1: Building a String with Local Functions
string FormatAddress(AddressDetail theAddress)
{
string result;
// ----- Local function that adds a comma when needed.
string AddComma(string origContent)
{
if (string.IsNullOrEmpty(origContent))
return "";
else if (origContent.Trim().EndsWith(","))
return origContent;
else
return origContent + ", ";
}
// ----- Build an address from parts.
result = theAddress.Line1;
if (!string.IsNullOrEmpty(theAddress.Line2))
result = AddComma(result) + theAddress.Line2;
if (!string.IsNullOrEmpty(theAddress.Unit))
result = AddComma(result) + "Unit " + theAddress.Unit;
if (!string.IsNullOrEmpty(theAddress.City))
result = AddComma(result) + theAddress.City;
if (!string.IsNullOrEmpty(theAddress.State))
result = AddComma(result) + theAddress.State;
if (!string.IsNullOrEmpty(theAddress.Zip))
result = (result + " " + theAddress.Zip).Trim();
return result;
}
My obvious phobia of the String.Join method notwithstanding, the FormatAddress function could’ve been designed with a traditional external method doing the comma stuff. Because local functions are essentially just methods, it shouldn't be surprising that an external variation would work. But local functions do have other advantages, including the ability to access local variables within their surrounding block. Consider a rewrite of the AddComma local function that references the outer result variable directly instead of through the origContent named parameter:
void AddComma()
{
if (string.IsNullOrEmpty(result))
result = "";
else if (!result.Trim().EndsWith(","))
result += ", ";
}
The calling code would need to change to avoid the lack of return value:
if (!string.IsNullOrEmpty(theAddress.Line2))
{
AddComma();
result += theAddress.Line2;
}
This all looks like magic, but the compiler is in effect adding a by-reference parameter to the local function, and passing the result variable through that reference:
// ----- Here's what the compiler does to the declaration:
string AddComma(ref string result)
// ----- And it's doing this to the calling code:
AddComma(ref result);
Another advantage of local functions is that they're limited in scope. Code outside of the FormatAddress method doesn't know that the AddComma local function even exists. This allows you to craft a method that has a highly specific use case, all without worrying if some future programmer is going to mistake it for a general-purpose function.
As with other recent grammar additions to C#, local functions provide new ways of doing things you could already do before. But in certain coding situations, local functions hide a lot of boilerplate scaffolding behind a direct, clean syntax, and isn't that what C# programming is supposed to be about? You know, more like BASIC.
About the Author
Tim Patrick has spent more than thirty years as a software architect and developer. His two most recent books on .NET development -- Start-to-Finish Visual C# 2015, and Start-to-Finish Visual Basic 2015 -- are available from http://owanipress.com. He blogs regularly at http://wellreadman.com.