...I would like to simplify my work by creating an object that I can simply instantiate, tell it on which IPEndpoint to listen and have it tell me whenever something of interest is going on...
After reading that statement I immediately thought "actors". Actors are very similar to objects except they only have a single input where you pass it the message (instead of directly calling the object's methods) and they operate asynchronously. In a very simplified example... you would create the actor and send it a message with the IPEndpoint and the address of the actor to send the result to. It goes off and does it work in the background. You only hear back from it when "something of interest" happens. You can instantiate as many actors as you need to handle the load.
I am not familiar with any actors libraries in .Net although I know there are some. I am familiar with the TPL Dataflow library (there will be a section covering it in my book http://DataflowBook.com) and it should be easy to implement a simple actor model with that library.
Dictionaries (C# or otherwise) are simply a container where you look up a value based on a key. In many languages it's more correctly identified as a Map with the most common implementation being a HashMap.
The problem to consider is what happens when a key does not exist. Some languages behave by returning null
or nil
or some other equivalent value. Silently defaulting to a value instead of informing you that a value does not exist.
For better or for worse, the C# library designers came up with an idiom to deal with the behavior. They reasoned that the default behavior for looking up a value that does not exist is to throw an exception. If you want to avoid exceptions, then you can use the Try
variant. It's the same approach they use for parsing strings into integers or date/time objects. Essentially, the impact is like this:
T count = int.Parse("12T45"); // throws exception
if (int.TryParse("12T45", out count))
{
// Does not throw exception
}
And that carried forward to the dictionary, whose indexer delegates to Get(index)
:
var myvalue = dict["12345"]; // throws exception
myvalue = dict.Get("12345"); // throws exception
if (dict.TryGet("12345", out myvalue))
{
// Does not throw exception
}
This is simply the way the language is designed.
Should out
variables be discouraged?
C# isn't the first language to have them, and they have their purpose in specific situations. If you are trying to build a highly concurrent system, then you cannot use out
variables at the concurrency boundaries.
In many ways, if there is an idiom that is espoused by the language and core library providers, I try to adopt those idioms in my APIs. That makes the API feel more consistent and at home in that language. So a method written in Ruby isn't going to look like a method written in C#, C, or Python. They each have a preferred way of building code, and working with that helps the users of your API learn it more quickly.
Are Maps in General an Anti-pattern?
They have their purpose, but many times they may be the wrong solution for the purpose you have. Particularly if you have a bi-directional mapping you need. There are many containers and ways of organizing data. There are many approaches that you can use, and sometimes you need to think a bit before you pick that container.
If you have a very short list of bi-directional mapping values, then you might only need a list of tuples. Or a list of structs, where you can just as easily find the first match on either side of the mapping.
Think of the problem domain, and pick the most appropriate tool for the job. If there isn't one, then create it.
Best Answer
You don't need to block in a console app anymore.
https://stackoverflow.com/questions/9208921/cant-specify-the-async-modifier-on-the-main-method-of-a-console-app
Even when you did, you could just call a MainAsync() from Main and block on that. There's no need to block on a HttpClient call and you shouldn't.
Your feeling is wrong. Even if the framework you are working in doesn't reuse the thread while you are awaiting, you can still make use of the thread while you await.