Sunday, October 18, 2020

Abstract Classes vs. Interfaces in C# - What You Know is Probably Wrong

A little over a year ago, C# 8 changed a lot of things about interfaces. One effect is that the technical line between abstract classes and interfaces has been blurred. It's been a year, and I haven't seen much online about this, so let's take a closer look at how things have changed.

To show the changes, here is a slide I used to use to show the differences. 4 of the 5 differences no longer apply:

Side-by-side list of differences between interfaces and abstract classes with 4 of the 5 differences being crossed out.

A lot of folks are not aware of these changes, so let's go through what we used to know about the differences between abstraction classes and interfaces and see if it still holds up today.

Note: For more information on the changes to interfaces in C# 8, you can take a look at the code and articles in this repository: https://github.com/jeremybytes/csharp-8-interfaces. There are also links to videos that demonstrate the new features (and some of the pitfalls).


Difference #1

An abstract class may contain implementation code. An interface may not have implementation code, only declarations.

Status: No Longer True

Interfaces in C# 8 can have implementation code -- referred to as "default implementation". For example, an interface method can have a body that provides functionality. This functionality is used by default if the implementing class does not provide its own implementation.

An interface can also provide default implementation for properties, but due to limitations with property setters, this only makes sense in specific circumstances.

More information: C# 8 Interfaces: Properties and Default Implementation.


Difference #2

A class may only inherit from a single abstract class, but a class may implement any number of interfaces.

Status: Still True

C# still has single inheritance when it comes to classes. But because a class can implement any number of interfaces, and those interfaces may have implementation code, there's a type of multiple inheritance that is available through interfaces.

When calling default implementation members, the caller must use the interface type (just like when a class has an explicit implementation). This avoids the "diamond problem", which is where two base classes provide the same method. By specifying the interface, this avoids the runtime having to make decisions of which method to use.


Difference #3

Abstract class members can have access modifiers (public, private, protected, etc.). Interface members are automatically public and cannot be changed.

Status: No Longer True

In C# 8 interface members can have access modifiers. The default is public (so existing interfaces will still work as expected). But interfaces can now have private members that are only accessible from within the interface itself. These may be useful for breaking up larger methods that have default implementation.

Note: Protected members are also possible, but because of the weirdness of implementation, I have not found a practical use for them.

More information: C# 8 Interfaces: Public, Private, and Protected Members.


Difference #4

Abstract classes can contain fields, properties, constructors, destructors, methods, events, and indexers. Interfaces can contain properties, methods, events, and indexers (not fields, constructors, or destructors).

Status: Mostly True

This is still mostly true. When it comes to instance members, interfaces can contain only properties, methods, events, and indexers. Instance fields and constructors are still not allowed.

But, interfaces can contain static members (see "Difference #5" below). This means that interfaces can have static fields, static constructors, and static destructors.


Difference #5

Abstract classes may contain static members. Interfaces cannot have static members.

Status: No Longer True

As noted above, interfaces in C# 8 can have static members. A static method in an interface works exactly the same way as a static method on a class. 

The same is true for static fields. Yes, fields are allowed in an interface, but only if they are static. As a reminder, a static field belongs to the type itself (the interface) and not any of the instances (meaning, instances of a class that implement the interface). This means that it is a shared value, so you want to use it with care. Note that this is exactly how static fields work on classes, so if you've used them there, you already know the quirks.

More information: C# 8 Interfaces: Static Members.

Also, having a "static Main()" method is now allowed in an interface. If you want to drive your co-workers crazy, here's one way to misuse this: Misuing C#: Multiple Main() Methods.


Technical Differences vs. Usage Differences

So if we look at the 5 technical differences between abstract classes and interfaces, 3 of them are no longer true, and 1 of them has some new caveats.

This really blurs the technical line between abstract classes and interfaces. When it comes to actually using abstract classes and interfaces, I'm currently sticking with my previous approach.

If there is *no* shared code between implementations...
I use an interface. Interfaces are a good place to declare a set of capabilities on an object. I like to use interfaces in this case (rather than a purely abstract class) because it leaves the inheritance slot open (meaning, the class can descend from a different base class if necessary).

If there *is* shared code between implementations...
I use an abstract class. An abstract class is a good place to put shared implementation code so that it does not need to be repeated in each of the descending classes.

Why not use interfaces with defaults for shared implementation? Well, there are a lot of interesting things that come up with default implementation, such as needing to refer explicitly to the interface and not the concrete type. Using the interface type is a generally a good idea (and I recommend that), but when it is forced on you, it can be confusing.

Another limitation has to do with properties. Because interfaces cannot have instance fields, there is no way to set up real properties using default implementation. Abstract classes can have fields, so we can put full properties and automatic properties into an abstract class that are then inherited by descending classes.

My approach is a personal preference based on how interfaces and abstract classes have been used in the past. I'm all for language and technique evolving, but this is a slow process. And until certain practices become widespread, I like to use "the path of least surprise". In this case, it means keeping a logical separation between abstract classes and interfaces even though the technical separation has been blurred.

Mix Ins
One other way that default implementation can be used is for mix ins. This is where an interface *only* contains implemented members. This functionality can then be "mixed in" to a class by simply declaring that the class implements the interface. No other changes to the class are necessary.

This is an interesting idea, and I still have to explore it further. Personally, I wish this functionality got a new keyword. It muddies things up a bit when we try to define what an interface is.

Wrap Up

So if we look at the 5 differences between abstract classes and interfaces, we find that most of them are not longer true starting with C# 8.

If you'd like to see the implications of these changes, take a look at the articles available here: https://github.com/jeremybytes/csharp-8-interfaces or here: A Closer Look at C# 8 Interfaces

And if you'd like a video walkthrough with lots of code samples, you can take a look at this video on YouTube: What's New in C# 8 Interfaces (and how to use them effectively). This is from a talk I did for the Phoenix AZ area user groups.


Happy Coding!

Friday, September 11, 2020

"await Task.WhenAll" Shows One Exception - Here's How to See Them All

When we use "await" in C#, we can use the typical try/catch blocks that we're used to. That's a great thing, and it makes asynchronous programming a lot easier. 

One limitation is that "await" only shows a single exception on a faulted Task. But we can use a little extra code to see all of the exceptions.

Let's take a closer look at what happens when we "await" a faulted Task and how we can take more control if we need it. 

Background

Tasks in C# are very powerful. They can be chained together, run in parallel, and set up in parent/child relationships. With this power comes a bit of complexity. One part of that is that tasks generate AggregateExceptions -- basically a tree structure of exceptions.

When we "await" a Task, it unwraps the AggregateException to give us the inner exception. This makes our code easier to program, and often there will only be one inner exception. But when there are multiple exceptions, we lose visibility to them.

This topic came up in a virtual training class that I did this past week. We were looking at parallel processing that included exception handling. We didn't have time to dig into this in the class, so I did some experimentation after.

Note: the code for this article (and the rest of the virtual training class) is available on GitHub: https://github.com/jeremybytes/understanding-async-programming.

The projects that we'll use today are "People.Service" (the web service), "TaskAwait.Library" (a library with async methods), and "TaskException.UI.Console" (a console application).

If you're completely new to Task, you can check out the articles and videos listed here: I'll Get Back to You: Task, Await, and Asynchronous Methods in C#.

Parallel Code

For this sample code, we have a console application that calls a web service multiple times. It is called for each record we want to display (for this scenario, we might only want a handful of records rather than the entire collection).

Starting the Service

If you want to run the application yourself, you'll need to start the "People.Service" web service. To do this, navigate your favorite terminal to the "People.Service" folder and type "dotnet run".


This shows the service listening at "http://localhost:9874". The specific endpoint we're using here is "http://localhost:9874/people/3" (where "3" is the id of the record that we want).

Running in Parallel with Task

The "TaskException.UI.Console" project has a single "Program.cs" file. This is where we have our parallel code. Here is an excerpt from the "UseTaskAwaitException" method (starting on line 61 of the Program.cs file):

This section sets up a loop based on the IDs of the records we want to retrieve.

The loop starts with a call to the "GetPersonAsyncWithFailures" method that returns a task (we'll look at this method in a bit). Note the "WithFailures" is there because I created a separate method to throw arbitrary exceptions.

The resulting task is added to the a task list that holds a reference to all of the tasks.

Then we set up a continuation on that task. When the task is complete, it will output the record to the console. Note that this is marked with "OnlyOnRanToCompletion", so this continuation only runs on success; it will not run if an exception is thrown.

As a last step in the loop, we add the continuation task to our list of tasks.

Because nothing is "await"ed in this block of code, all of the tasks (9 altogether) are generated very quickly without blocking. So these will run in parallel.

Outside of the loop, we use "await Task.WhenAll(taskList)" to wait for everything to complete.

This will wait for all 9 tasks to complete before continuing with the rest of the method.

The Happy Path

When no exceptions are thrown, this outputs 9 records to the console. Here is a screenshot from the "Parallel.UI.Console" project (which calls a method that does *not* throw exceptions):

This shows us all 9 records. Now let's see what happens when there are exceptions.

Throwing Arbitrary Exceptions

As mentioned above, the method we're calling for testing throws some arbitrary exceptions. Our method is called "GetPersonAsyncWithFailures". Here is an excerpt from that (starting on line 75 of the TaskAwait.Library/PersonReader.cs file):

This method has two "if" statements that throw exceptions. If the "id" parameter is "2", then we throw an InvalidOperationException. If the "id" parameter is "5", we throw a "NotImplementedException".

These exceptions are completely arbitrary. We just need multiple calls to this method to fail so we can look at the aggregated exceptions.

"await Task.WhenAll" Shows One Exception

As a reminder, here's the code that we're working with (starting on line 61 of the Program.cs file in the "TaskException.UI.Console" project):

As noted above, the "foreach" loop will run the async method 9 times (with 9 different values). Two of these values will throw exceptions.

We don't need to look for these exceptions in the continuation since we marked it with "OnlyOnRanToCompletion" -- the continuation only runs for successful tasks.

The good news is that when we "await" a faulted Task (meaning, a Task that throws an exception), the exception is automatically thrown in our current context so that we can use a normal "try/catch" block.

When we use "await Task.WhenAll(tasklist)", it will throw an exception if any of the tasks are faulted.

Since we have 2 faulted tasks here, that's exactly what happens.

Here's a bit more of the "UseTaskAwaitException" method (starting on line 76 of the Program.cs file);

The top of this block shows the "await Task.WhenAll" call. Then we have catch blocks to handle cancellation or exceptions. The "OutputException" method shows several of the properties of the exception on the console (the details are in the same "Program.cs" file if you're interested).

Let's run this method to see what happens. To run the application, navigate (using your favorite terminal) to the "TaskException.UI.Console" folder and type "dotnet run".

This will bring up the console application menu:

The code we've seen so far is part of option #1 "Use Task (parallel - await Exception)". If we press "1", then we get the following output:

The output shows us 7 records (since 2 of the 9 failed). And it shows us the "InvalidOperationException" that is thrown for ID 2.

But it does *not* show us the other exception that was thrown.

We'll dig the exception out of the Task directly and look at its values. Then it will be a little more clear what "await" is doing here.

Getting All of the Exceptions

As mentioned at the beginning, Task is very powerful. Because of the various ways that we can chain tasks or run them in parallel, Task uses what's known as an "AggregateException". This contains all of the exceptions that happen in relation to the task.

To see this we will do something a little different with the "Task.WhenAll" method call.

"Task.WhenAll" returns a Task (and this is why we can "await" it). But we can also set up our own continuation to see the details of what happened.

We have a separate method where we've done just that. Let's look at the loop on the "UseTaskAggregateException" method (starting on line 106 of the Program.cs file):

The "foreach" loop is that same as the prior method, but the "await" is different.

The continuation that is set up here is marked as "OnlyOnFaulted", so it will only run if at least one of the Tasks throws an exception. The "task.Exception" property has the AggregateException that is mentioned above.

The "OutputException" method show different parts of the exception. The details are in the "Program.cs" file if you're interested. Otherwise, we can look at the output to see what is in the exception.

Note: we are still using "await", but we are not awaiting the "WhenAll". Instead, we are awaiting the continuation task on "WhenAll". This will still pause our method until all of the tasks are complete (including this final continuation). It can get a bit confusing when mixing "await" with directly using Task.

If we re-run the console application and choose "2" this time (for "Use Task (parallel) - view AggregateException"), we get the following output:

The output shows the 7 successful records, but the exception is different from what we saw earlier.

Instead of the "InvalidOperationException", we get an "AggregateException". The exception message shows us that "One or more errors occurred", and it also concatenates the inner exception messages.

Also note that there is an "InnerException" property that has the "InvalidOperationException". This is the exception that is shown when used "await" above.

But an AggregateException also has an "InnerExceptions" property (which is a collection). If we iterate over this, we find both of our exceptions: the "InvalidOperationException" and the "NotImplementedException".

When we use a continuation to check a Task exception directly, we get more details than when we use "await". The good news is that we can iterate over these inner exceptions and pass them along to our logging system.

Changing the Order

I was wondering if the exception that "await" shows for this code is deterministic. And it turns out that it is.

As an experiment, I reversed the order of the task list before passing it to "WhenAll". Here's the code for the "await" block:

With the reverse in place, we get a different output:

The output shows us the other exception that was generated - the NotImplementedException for ID 5.

We can also reverse the task list before sending it to the continuation:

The output shows us a bit better what is happening:

By looking directly at the AggregateException, we can see that the "InnerException" is now the one related to ID 5 (which is shown when we "await" things above).

Looking at the "InnerExceptions" collection, we see that the order is reversed. The ID 5 exception is first, and the ID 2 exception is second.

So when it comes to awaiting "Task.WhenAll", it appears that the exception we ultimately get is based on the order of the Tasks that are passed to the "WhenAll" method. Fun, huh?

Is This Important?

So how much do we need to care about this? The typical answer I would give is "probably not much". Tasks are extremely powerful, but we often do not need that power. And that's why "await" exists.

"await" gives us the 95% scenario. A common scenario I have is that I need to call an asynchronous method, wait for it to finish, do something with the result, and handle exceptions. If I only need to wait for one method at a time, then "await" works perfectly for this.

When we need to step outside of this scenario, it's good to understand how to use Task more directly. By understanding the process of setting up continuations and unwrapping AggregateExceptions, we can take more control of the situation when we need it.

Often we only care that "an exception happened". This lets us know that our application is in a potentially unstable state, and we need to handle things accordingly. We do not care about all of the details of what went wrong, and "await" is all we need. Whether we need to dig deeper depends on the needs of the users and the application.

If you want more information about using Task and await, you can check out the articles and video series on my website: I'll Get Back to You: Task, Await, and Asynchronous Methods in C#.

Happy Coding!


Wednesday, August 26, 2020

CodeTour and Visual Studio Code

I've been experimenting with CodeTour, an extension for Visual Studio Code that allows you to annotate code and create a "tour" through a project.

My initial reaction is that this can be an effective education tool

But after using it a bit more, I can also see it as a way to document code. It sits somewhere between large comment blocks in the code and having a separate document with details. Lots of things to think about.

CodeTour in Action

If you want to see a CodeTour in action, just install the extension from the Visual Studio Marketplace (CodeTour) and grab this repository from GitHub: ASP.NET MVC Conventions. When you open the repo for the first time, it will ask if you want to run the CodeTour.


Otherwise, you can use the "CodeTour" section in the Visual Studio Code Explorer.


From there, you will be guided through a tour of ASP.NET MVC conventions based on the default template.

You can also expand the tree and jump to specific steps:



Features

There are quite a few features that I used here. 

Annotating a Line of Code
The most basic is to point to a line of code and provide some information about it.


Here we have a pop-up in the middle of the editor that allows you to provide some more information. The content is Markdown, so you can use various types of formatting.

The arrow buttons at the top of the box let you move from step to step (there are keyboard shortcuts for navigating as well).

Annotating a Block of Code
In addition to pointing to a line, you can also annotate a block of code with a highlight.


Shell and Visual Studio Code Commands
Some of the cooler features allow you to put in shell commands to run from the terminal and also commands that run from the Visual Studio Code command palette.


This has a link for "dotnet run" which will execute in the terminal (this starts the web application).

The second link will open a browser to http://localhost:5000. But instead of being just a link (which prompts whether you really want to open it), this uses a Visual Studio Code command to open the URL (so there is no extra prompt).

Here's a clip of both links in action.


Here's what the Markdown looks like:


The shell command is prefixed with ">> ". This will run that command in the terminal.

The bottom line is similar to a standard Markdown link, with the text in square brackets and the target in parentheses. But instead of having "http:", the target has "command:". 

The details for this are in the documentation under "Command links": https://marketplace.visualstudio.com/items?itemName=vsls-contrib.codetour.

As a side note, I used a non-breaking space ( ) in the non-link version of the URL (the line between the clickable bits). This prevents the Markdown interpreter from automatically creating a link out of this.

Content Steps
Another feature I used is a "Content Step". This provides a block of Markdown that is not associated with a specific block of code. I used this for the introductory steps and the summary step.

Directory Steps
Sometimes we want to point at a folder rather than a code file. A "Directory Step", will open up the Visual Studio Code Explorer to a relevant folder, and you can provide annotations for that. In this case, I pointed out several of the ASP.NET folders, including "Controllers", "Views" and "Shared".

Other Features
There are some other features that I haven't used yet. You can check out what's possible in the documentation: https://marketplace.visualstudio.com/items?itemName=vsls-contrib.codetour.

Wrap Up

This is interesting from an education and training perspective. In this case, I was able to take an article (The Secret Code: ASP.NET MVC Conventions) that points to a GitHub repo, and turn it into something more interactive that walks through the repo.

But this is also interesting from a documentation perspective. This could be used as a way to annotate a code base so that when a new developer comes on, they can be led through the relevant points. It can also be seen as in-line documentation for areas where we don't want to pollute our code with large comment blocks, but want something a bit better than a separate document that has all of the relevant information. This can give us an "in between" where the documentation is available with the code, but it is not directly mixed in with the code.

Take a look, and see what you can do with it.

Happy Coding!

Sunday, August 9, 2020

Video: A Tour of Go for the C# Developer

I've found a lot of interesting features and ideas in Go (golang). By exploring these, I've got several things to try out in my primary language (which is currently C#).

If you're curious about Go, I've put together a tour of the language targeted at the C# developer. By building an application that makes concurrent service calls, we can see some of the similarities and differences between C# and Go.

Watch it on YouTubeA Tour of Go (golang) for the C# Developer

There's also a repo with all of the code: https://github.com/jeremybytes/go-for-csharp-dev

Features

Here are a few of the things I find interesting about Go:

Opinionated Syntax
Go syntax is C-like, but it has some strong opinions. For example, opening curly braces must always go on the same line as the function (or "if", or other block statement). In addition, the curly braces are never optional for an "if" statement, regardless of how many lines are part of the body.

No Unused Variables
Go does not allow for unused variables. If you declare a variable and do no use it, you get a compiler error. I like this idea since it gets rid of code that isn't used. This also encourages the use of the "blank identifier" (an underscore) which acts as a discard.

Baked-in Concurrency
Concurrency is baked right into the Go language -- in fact, they are called "goroutines" and you use "go" to start one. Channels can be used to communicate between a goroutine and other functions. Seeing how they work makes the idea of Channels in C# (added in .NET Core 3.0) a bit more interesting to explore.

Deferred Calls
Go has a "defer" statement. For example, "defer channel.Close()" will call the "Close()" method at the end of the function (however it exits). This is similar to a "finally" in C#, but it can be used anywhere in a function or method.

Error Handling
Go encourages the the use of "error" instead of exception (which is called "panic" in Go). An error is really just a message. A common pattern is for a function to return a data item plus an error. If the error is populated, then something went wrong. The idea is that we treat errors as messages to be handled in our own code rather than something to be handled by the language or framework. (And "panic" does exist if we have a truly panic-inducing circumstance.)

Multiple Return Values
As mentioned above, it's common for a function to return a data item plus an error. Go supports multiple return values. So we can return multiple values without dealing with explicit tuples.

Interfaces
Interfaces in Go are explicitly declared but implicitly implemented. Meaning, if we have an interface that declares a "String()" method, a type can implement that the interface by simply providing a "String()" method. It does not need to specifically say it implements the interface, it just needs to provide the members. This encourages a different way of using defining and using interfaces. I wrote a bit about my initial experiences here: Go and Interfaces.

Check Out the Video

In building a small application (about 100 lines), the video looks at all of these features, plus a few more. Be sure to check it out:

Happy Coding!

Thursday, July 30, 2020

Go and Interfaces

I've restarted my adventures in Go (golang). Based on the experience that I've had in the meantime, I have a better appreciation of many of the language features.

Specifically, I've become a fan of how Go handles interfaces compared to C#. (Interfaces in C# have become a bit of a complicated mess. I've written a series of articles about the recent changes (A Closer Look at C# 8 Interfaces) and have done some recent user group talks on the changes that come out over 2 hours long (recording of the Tulsa .NET User Group)).
I find that the Go minimalist approach to interfaces can make things easier to code.
So, let's look at an example that I recently worked through.

Go Interfaces

Before looking at the example, let's look at how interfaces work in Go.

Here is the "io.Writer" interface that is part of the Go io package (docs: io.Writer):

type Writer interface {
    Write(p []byte) (n int, err error)
}

This declares the "Writer" interface which has one method: "Write".

The "Write" method takes a byte array as a parameter, and returns an int (the number of bytes written) as well as an error (in case any errors occur). As a side note, the "int, error" return type is a common pattern in Go.

The interesting bit (and where things differ from C#) is how types implement this interface. Any type that wants to implement the "io.Writer" interface just needs to have the "Write" method itself. It does not need to declare itself as an "io.Writer", it just has to have the method.

This means that any type with a "Write" method that matches this signature is automatically an "io.Writer".

Let's see this in action.

Lissajous Curve

My example code comes from The Go Programming Language by Alan A. A. Donovan and Brian W. Kernighan (Amazon link). One of the early tutorials is to create a Lissajous curve. Here's an gif showing what that is:


The code is a bit of math that gets output to an animated gif. I have this code on GitHub (jeremybytes/go-programming-language), specifically, we'll look at the "ch1/lissajous" project. The code is all in a single file, "main.go" (link on Github: main.go).

Here's the code for creating the gif (starting at line 44 of main.go):


This has the constants collapsed, but you can check the full code for details.

There are 2 bits I want to point out here: First, the "lissajous" function takes an "io.Writer" as a parameter. Finally, the "gif.EncodeAll" method at the bottom uses the "io.Writer" parameter to produce the output.

Using the Lissajous Function

We use the lissajous function in the "main" function (the "main" function gets executed when we run the Go program).

The most basic call is on line 41 (from main.go):

lissajous(os.Stdout)

This passes "os.Stdout" to the "lissajous" function. The result is that the gif is written to Standard Out (which is the console if we just run this as a console application).

Writing a gif to the console is not very useful, but we can pipe the output of our program to a file like this:


.\lissajous.exe > out.gif

This will pipe the output of the program to a file named "out.gif".

A few notes here. First, the ".\lissajous.exe" command has a backslash. That's because I'm running this on Windows 10. Next, this command doesn't actually work in PowerShell on Windows 10. The "out.gif" file is created, but it's not an actual gif (meaning, nothing can display it). I'm sure this is PowerShell weirdness (I show how I got around this in just a bit). When I run this command on macOS (with a slash instead of a backslash), it works just fine. The "out.gif" file is a valid animated gif.

A Web Server

One of the cool things about Go is that we can spin up a web server with just a few lines of code. Here is a bit of code that we can add to the "main" method to serve up the animated gif (starting on line 27 of main.go):


This sets up an HTTP handler that calls the "lissajous" method. On the second line, note that the handler is a function that takes an "http.ResponseWriter" as a parameter. This type implements the "io.Writer" interface, so all we need to do is pass it to the "lissajous" method.

We can run the application with the "web" parameter like this:


.\lissajous.exe web

Then, we can navigate a browser to "http://localhost:8000" to get this result:


Pretty cool. (Okay, so it's cooler if you do it yourself because this screenshot doesn't show the animation.)

Note: To kill the web server, use "Ctrl+C" on the console.

Appreciating Interfaces

So now we get to the part where I learned to really appreciate interfaces. As I noted above, piping the Standard Out to a file doesn't work in PowerShell for some reason (although it does work on the macOS terminal). I decided to add another parameter to the application so that it would go straight to a file.

So just like the "web" parameter would start a web server, a "file" parameter would output it to a file.

And this is where I got a bit confused and started fumbling a bit. I was thinking a bit more linearly, so I was thinking about how I could create a file, open the file, and then write whatever came from the "lissajous" function to that file.
I was looking up examples on how to write to a file in Go. But I was on completely the wrong track for this application.
I was getting a bit frustrated because it seemed way too difficult. And that's when I finally looked up exactly what "io.Writer" was (back to the doc: io.Writer):

type Writer interface {
    Write(p []byte) (n int, err error)
}

That's when I realized that I was dealing with an interface, and not some base type. (I was too mired in C# thinking at the time.)

So then, I looked at the "os.File" type (doc here: os.File). Scrolling down a bit, I saw that "File" has a "Write" method:


This method signature matches the io.Writer interface!

Once I had made the connection, the code fell together pretty easily. Here is that part of the "main" method (starting with line 35 in main.go):


The "os.Create" returns a File pointer and an error (docs: os.Create). I assign the file pointer to "gf" (for "gif file") and use an underscore to discard the error object.

Note: I'm skipping error checking here. If you try to compile when you have an unused variable, the compile will fail (a nice safety net). This means that you will often see discards in function and method calls. In this case, we are not checking errors, so we discard the error item that comes back from "os.Create".
Since the file is an "io.Writer", we can pass it directly to the "lissajous" function. This is very convenient (once you start thinking this way).
Now, we can run the application with the "file" argument:


.\lissajous.exe file

And we get a valid animated gif on the file system named "out.gif".


Wrap Up

So, I took the long way around to a convenient solution. But it made me appreciate interfaces in Go that much more.

I really like the minimal approach to interfaces in Go. I don't have to look at a type signature to figure out "is this an io.Writer?" All I need to do is look at the type members themselves. If it has members that satisfy the interface, then I know I can use it wherever that interface is called for.

There are a lot of things to get used to in Go, and there are some conventions to learn. As an example of a convention, a method / function / type that starts with a capital letter is exported (similar to "public" in C#; it can be used from other packages). If it starts with a lower-case letter, it is not exported (similar to "private" in C#).

There are lots of interesting ideas in Go. I'm enjoying my second look at the language.

Monday, June 29, 2020

Misusing C#: Multiple Main() Methods

Sometimes you run across things you should probably never do in real life but are possible in your development environment. Having more than one "Main()" method in an application is one of those things in the .NET world.
An application can have more than one "static Main()" method. The only condition is that you must indicate which is the "real" entry point (through a project setting or on the command line).
Note: the code for this project is on GitHub: jeremybytes/misusing-c-sharp. Today's project is in the "MultipleMain" folder, but there will be other projects coming in the near future.

Let's look at a program that misuses this feature.

A Straight-Forward Console Application?
This is a console application with the following "Program" class (from the Program.cs file in the "MultipleMain" project mentioned above):

The "Program" class has a "static Main()" method that puts "Hello World!" on the console. If you supply a command-line parameter (such as your name), then it will say hello to you.

Since this is a .NET Core 3.1 project, lets go to the command line to build and run the application.

Build & Run
On the command line, a "dotnet build" will build the application.

Then we can use "dotnet run" to run the application.

Wait, what?

This isn't what I expected at all. What's happening?

static Main() Methods
This application misuses the normal entry point for .NET applications: "static Main()".
  1. "static Main()" is the entry point to an application.
  2. "static Main()" can be in any class.
  3. In C# 8, "static Main()" can be in an interface.
  4. You can only have one "static Main()" method per application.*
*Note: if you have more than one "static Main()" you must specify which one is the application entry point.

See that * above? It's actually very important. Normally, we are only allowed to have one "static Main()" in an application, BUT we can have more than one as long as we also tell the application which one is the entry point. This can be done with a compiler directive or with an application property. (We'll see this below.)

Another static Main() Method
Our project has another "static Main()" buried in the "DataTypes.cs" file in the "Library" folder.

I did a little bit of obfuscation with naming just for extra fun. In addition, if we open the "DataTypes.cs" file, we'll see some innocuous data types at the top of the file (from the DataTypes.cs file in the "Library" folder):

But if we scroll down a bit, we find another "static Main()" in the "IWhyNot" interface (from the same file):

This method generates 5000 random numbers before printing out "This is not what I expected!".

Note: I named this the "IWhyNot" interface in honor of my favorite comment in the language design notes. You can check out this article for more information: C# 8 Interfaces: Static Main() -- Why Not?

This is the current entry point for the application.

Specifying the "real" static Main() Method
The reason this application works this way (and builds at all) is because there is a "StartupObject" attribute in the project file.

Here is the project file (from the MultipleMain.csproj file on GitHub):

Notice the "<StartupObject>" tag. This has the fully-qualified name for the type that contains the "real" static Main() method. In this case, it is "MultipleMain.DataTypes.IWhyNot".

Overriding the StartupObject at Build
It is possible to override the "StartupObject" on the command line. Here's a new "dotnet build" that shows this.

dotnet build -p:StartupObject=MultipleMain.Program -t:Rebuild

The "dotnet build" command has a couple of parameters. The first is "-p:StartupObject=MultipleMain.Program".

The "-p" parameter adds a "property" to the build. Here we are specifying the value to use for the "StartupObject" property (this is the same property that we saw in the project file). The new value for this is "MultipleMain.Program" -- the "Program" class that we saw first.

The second parameter that we have is "-t:Rebuild"

The "-t" parameter sets a "target". This is important for our scenario.

Normally, when we "dotnet build", it uses the same methodology as "Build" from Visual Studio. If all the files in a project are unchanged, the project is not re-built. If we were to use a normal build here, then the executable would *not* be rebuilt because none of the source files (or even the project file) have changed.

So we need to specify that we want to do a "Rebuild". This is the same as "Rebuild" in Visual Studio. All of the projects are re-built whether the source files have changed or not.

So this "dotnet build" command forces a rebuild of our project using the "StartupObject" specified (and the command-line parameter overrides any values from the project file).

Now when we run the application, we get the expected results:

Using "dotnet run" gives us the expected "Hello World!". And if we use "dotnet run -- Jeremy" (which passes "Jeremy" to the program as a command-line parameter), we get the output "Hello, Jeremy".

Note: "dotnet run" will do a build if necessary. Like a normal build, this is based on changes to the source files. Since the source files are unchanged here, "dotnet run" uses the existing executable / assembly.

More Options
Because we have the command-line option for specifying the "StartupObject", we do not need to put it into the project file.

As an experiment, let's remove the "StartupObject" attribute from the "MultipleMain.csproj" file and try to build the application in Visual Studio.

We get the same error using "dotnet build" (with no additional parameters):

This gives us an error because we have not specified the entry point for the application. If we follow the error code (CS0017), we come across this article: -main (C# Compiler Options).

This shows us that we can use a "-main" command-line parameter when we're using csc.exe (the command-line C# compiler). The "-main" option is not exposed in MSBuild, and so is not available using the "dotnet build" command.

The other option in the article is to set the "StartupObject" property in the project file. And that's the route that we took here.

If we use "dotnet build" *with* the StartupObject property, then we can build successfully. We do not need the "StartupObject" attribute in the project file at all (as long as we supply the value at compile-time).

Be Kind
Please don't do this to your co-workers. Many developers are not aware of this feature. So if you bury a "static Main()" somewhere in the code and then set the "StarupObject" in the project file, another developer will probably spend a very long time figuring out what's going on.

Remember: Just because you can doesn't mean that you should.

Happy Coding!

Thursday, May 7, 2020

Cross-Platform Build Events in .NET Core using MSBuild

In the last article, we looked at post-build events and .NET Core -- specifically how the '$(SolutionDir)' and other macros based on the solution folder do not work when we do project-based builds. Instead, we should use folders relative to the project.

    xcopy "$(ProjectDir)AdditionalFiles\*.*" "$(TargetDir)" /Y

This works fine for Windows-based projects (like the WPF project used in the previous article), but it fails when we move to a cross-platform environment. "xcopy" doesn't exist on macOS or Linux.

Fortunately, we can dig into MSBuild a little deeper and create tasks that copy files in a platform-agnostic manner. Here's a preview of the build task that does the same things as above:

Let's dig into some code.

MSBuild Tasks
MSBuild (the build tool in the .NET world) has a number of Tasks that we can use as part of the build process. These go right into our project files.

In fact, when we add a post-build event through the Visual Studio project settings editor, we end up with an MSBuild task in the project file. For example, our "xcopy" command that we saw above:


Ends up in the project file looking like this:


  <Target Name="PostBuild" AfterTargets="PostBuildEvent">
    <Exec Command="xcopy &quot;$(SolutionDir)AdditionalFiles\*.*&quot; &quot;$(TargetDir)&quot; /Y" />
  </Target>

This uses the "Exec" task to run a command at the command prompt.

So we're already using MSBuild tasks. Fortunately for us, there are other tasks to choose from besides "Exec".

The Copy Task

On the Microsoft Docs site, we can see the tasks that are built in: MSBuild task reference. One of those is the Copy task (docs reference for Copy task).

For today's examples, I'll be showing the cross-platform examples from my C# Interfaces talk: https://github.com/jeremybytes/understanding-interfaces-core31. There are 2 specific project files that I'll link to as we go.

Let's look at our new copy task and see what's going on (this code is available in the "PeopleViewer.csproj" file at this link: PeopleViewer.csproj -- specifically lines 17-26):


First, let's look at the "Target". This is a grouping that tells when this task is going to run. Notice that "AfterTargets" parameter is set to "Build". So this will run after the build. If the build fails, then this will not run.

Inside, we have the "Copy" task itself. We are using 3 parameters: (1) SourceFiles, (2) DestinationFolder, and (3) SkipUnchangedFiles.

"SourceFiles" is a required parameter. This is an array that specifies the files that we want to copy. Since this is an array, we create our own element called "DataFiles". The "Include" parameter has a list of the files that we want to include. We can still use the build macros, so "$(ProjectDir)" works just fine here. In this case, we only have one item in our array (our one item happens to include a wildcard "*.*").

The value for the "SourceFiles" parameter is the item created in the ItemGroup above. We reference it with using "@(DataFiles)".

As an alternative to using wildcards, we can specify individual files separated by semi-colons. An example of this is shown on the Microsoft Docs site: Example (with multiple files) -- screenshot below (but be sure to follow the link):


"DestinationFolder" is a kind-of-required parameter. This has the destination for the files to be copied. If the folder does not exist, then it is created automatically. Our value for this folder is the "$(TargetDir)" that we had in the "xcopy" version.

"DestinationFiles" is a kind-of-required parameter. You'll notice that we do not have a "DestinationFiles" parameter for our copy task. This is because we can use either "DestinationFolder" or "DestinationFiles", but not both (and we need at to have at least one of them). The "DestinationFiles" parameter is also an array, and it expects a one-to-one mapping of items from the "SourceFiles" array.

"SkipUnchangedFiles" is an optional parameter. This will not copy files that are unchanged between the source and the destination. It determines this based on the file size and the timestamp.

There are a number of other interesting parameters, so you should take a look at the docs. For example, "UseHardlinksIfPossible" will create links to the files rather than copying them.

Recursive Copying
Many times we need to copy entire folder structures (including subfolders). The good news is that we can do that with the copy task as well. For more information, take a look at the example on the Microsoft Docs site: Example (recursive copy) -- screenshot below (but be sure to follow the link):


A Lot of Effort?
This seems like a lot of effort compared to our previous post-build event. With the post-build event, we can use the Visual Studio project editor to build our commands and insert macros. Here, we need to edit our project files by hand. But it is totally worth it. This command works on Windows, macOS, and Linux -- and yes, I have tried it in all of those environments.

Before I found the MSBuild tasks, I was thinking about how I would get around this. Could I put in different commands (like using "xcopy" for Windows and "cp" for macOS), and then add some sort of conditional? That is technically possible, but it is a lot more brittle when all I need to do is copy some files.

I definitely prefer to use the build task.

OS-Specific Tasks
I ran into an issue where I needed to copy different files based on the OS, so I ended up digging into conditional MSBuild tasks.

For my specific issue, I have a library that uses SQLite to access a local database file. I need to do runtime binding to this library, so I need all of the required files in a shared location. The problem is that SQLite has different runtime files for each environment. For Windows, it uses "e_sqlite3.dll", for macOS it uses "libe_sqlite3.dylib", and for Linux it uses "libe_sqlite3.so". I needed to figure out how to get the right file to the output folder.

The MSBuild tasks for this are in the "PersonReader.SQL.csproj" file which is on GitHub: PersonReader.SQL.csproj.

Windows-Specific Task
To get started with conditional tasks, let's look at the task that copies over the Windows-specific SQLite file (this is on lines 31-41 of the PersonReader.SQL.csproj file):


Overall, this task looks similar to what we saw above. One difference is that the Target has a "Condition" parameter.

Condition=" '$(OS)' == 'Windows_NT' "
"Condition" lets us put in a restriction on when this task will run. For this example, we can look at the '$(OS)' value to see if we are running under Windows. If so, then it runs the copy task.

For more details on the types of conditions that we can use here, check out the MSBuild conditions article on Microsoft Docs.

macOS-Specific Task
It seems like doing the same thing for macOS should be straight-forward: just change the condition to use "macOS". Unfortunately, that does not work here. The '$(OS)' values are not specific enough for what we need. If we run on macOS, we get the value of "Unix" (since macOS is a Unix-based system). The problem is that for Linux, the value is also "Unix". So the "$(OS)" value is not helpful.

Fortunately, there is another option. Here is the code (from lines 43-53 of the same PersonReader.SQL.csproj file):


This condition is a bit harder to understand because things are fully-qualified. So before we look at this condition, let's look at this another way: as C# code.

Checking the OS in C#
If we wanted to check for the OS in C#, we can use the "RuntimeTypeInformation" class that is part of System.Runtime.InteropServices. Here's the code:


    if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) {}

The "IsOSPlatform" method takes an enum value (OSPlatform) as a parameter. So we can use this method to check whether something is running under Windows, macOS (i.e. OSX), or Linux.

For those of you who are new to macOS, the operating system was renamed from "OSX" to "macOS" a few years back. Since it is generally a bad idea to change enums that are already in use, we'll see "OSX" in various places when referencing the Apple OS.

Back to the MSBuild Condition
The condition that we have in the MSBuild task above uses this same "IsOSPlatform" method, but the syntax is a little different because it is in the MSBuild format and also because the names are fully-qualified.

Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::OSX)))'"
If we pull out the fully-qualified parts, we get something a bit more readable:

Condition="'$([RuntimeInformation]::IsOSPlatform($([OSPlatform]::OSX)))'"
The syntax is still different, but it's easier to see that we are calling the "IsOSPlatform" method on the "RuntimeInformation" class, and that we are passing in the equivalent of "OSPlatform.OSX" as a parameter.

Now this will not work unless it is fully-qualified, so we need to use the long version in our MSBuild task.

Linux-Specific Build Task
The task for Linux is similar to the macOS task. If you're interested, you can look at the code in the PersonReader.SQL.csproj file (lines 55-65).

Wrap Up

Now we have a post-build copy command that works cross-platform. And we also have some conditional commands that run based on what operating system we are building on. This gives us quite a bit of flexibility in building truly cross-platform applications.

MSBuild is quite an extensive system, and there are tons of options. The MSBuild task reference has a list of built-in tasks. In addition, you can create custom build tasks if needed (although that is something I am not likely to do myself).

One of the most interesting things to me about .NET Core is the cross-platform capabilities. As I move my applications forward, I think about how I can make them easier to run cross-platform. This gives a bigger audience for the applications and also provides different options for deployment.

Copying files in a way that is friendly to multiple operating systems is just one of the things we need to think about when creating cross-platform applications. Be sure to check back; I'll continue to document things I come across as I move projects over.

Happy Coding!