Clever little C# and ASP.NET Core features that make me happy
I recently needed to refactor my podcast site which is written in ASP.NET Core 2.2 and running in Azure. The Simplecast backed API changed in a few major ways from their v1 to a new redesigned v2, so there was a big backend change and that was a chance to tighten up the whole site.
As I was refactoring I made a few small notes of things that I liked about the site. A few were C# features that I'd forgotten about! C# is on version 8 but there were little happinesses in 6.0 and 7.0 that I hadn't incorporated into my own idiomatic view of the language.
This post is collecting a few things for myself, and you, if you like.
I've got a mapping between two collections of objects. There's a list of all Sponsors, ever. Then there's a mapping of shows where a show might have n sponsors.
Out Var
I have to "TryGetValue" because I can't be sure if there's a value for a show's ID. I wish there was a more compact way to do this (a language shortcut for TryGetValue, but that's another post).
Shows2Sponsor map = null;
shows2Sponsors.TryGetValue(showId, out map); if (map != null) { var retVal = sponsors.Where(o => map.Sponsors.Contains(o.Id)).ToList(); return retVal; } return null;
I forgot that in C# 7.0 they added "out var" parameters, so I don't need to declare the map or its type. Tighten it up a little and I've got this. The LINQ query there returns a List of sponsor details from the main list, using the IDs returned from the TryGetValue.
if (shows2Sponsors.TryGetValue(showId, out var map)) return sponsors.Where(o => map.Sponsors.Contains(o.Id)).ToList(); return null;
Type aliases
I found myself building JSON types in C# that were using the "Newtonsoft.Json.JsonPropertyAttribute" but the name is too long. So I can do this:
using J = Newtonsoft.Json.JsonPropertyAttribute;
Which means I can do this:
[J("description")]
public string Description { get; set; }
[J("long_description")] public string LongDescription { get; set; }
LazyCache
I blogged about LazyCache before, and its challenges but I'm loving it. Here I have a GetShows() method that returns a List of Shows. It checks a cache first, and if it's empty, then it will call the Func that returns a List of Shows, and that Func is the thing that does the work of populating the cache. The cache lasts for about 8 hours. Works great.
public async Task<List<Show>> GetShows()
{
Func<Task<List<Show>>> showObjectFactory = () => PopulateShowsCache();
return await _cache.GetOrAddAsync("shows", showObjectFactory, DateTimeOffset.Now.AddHours(8));
}
private async Task<List<Show>> PopulateShowsCache()
{
List<Show> shows = shows = await _simpleCastClient.GetShows();
_logger.LogInformation($"Loaded {shows.Count} shows");
return shows.Where(c => c.Published == true && c.PublishedAt < DateTime.UtcNow).ToList();
}
What are some little things you're enjoying?
Sponsor: Manage GitHub Pull Requests right from the IDE with the latest JetBrains Rider. An integrated performance profiler on Windows comes to the rescue as well.
About Scott
Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.
About Newsletter
List<Show> shows = shows = await _simpleCastClient.GetShows();
public async Task<List<Show>> GetShows()
{
Func<Task<List<Show>>> showObjectFactory = () => PopulateShowsCache();
return await _cache.GetOrAddAsync("shows", showObjectFactory, DateTimeOffset.Now.AddHours(8));
}
Can be ever so slightly simplified to this code:
public Task<List<Show>> GetShows()
{
Func<Task<List<Show>>> showObjectFactory = () => PopulateShowsCache();
return _cache.GetOrAddAsync("shows", showObjectFactory, DateTimeOffset.Now.AddHours(8));
}
You can still await GetShows(), but you've saved yourself a whole two words worth of typing.
https://github.com/BigBabay/AsyncConverter
Absolutely great
Func<Task<List<Show>>> showObjectFactory = () => PopulateShowsCache();
creates a delegate over an anonymous method that invokes PopulateShowsCache.
Whereas
Func<Task<List<Show>>> showObjectFactory = PopulateShowsCache;
just creates a delegate over PopulateShowsCache.
@Paulo Morgado - thanks for that link, it's a fascinating read! I hadn't considered some of those subtleties.
out var makes you gain ONE line of code, not 5 as you show it. You could have written your first code extract like this
Shows2Sponsor map = null;
if (shows2Sponsors.TryGetValue(showId, out map))
return sponsors.Where(o => map.Sponsors.Contains(o.Id)).ToList();
return null;
But that's true: 4 to 3 lines does not seems as important as gaining 5 lines!
And by the way, you're not testing the same thing: in the code block, you were testing if map is null (and it can be null inside your dictionnary), thing that you'r not testing in the second code bloc.
So you should have written the second bloc like that:
shows2Sponsors.TryGetValue(showId, out var map);
if (map != null)
return sponsors.Where(o => map.Sponsors.Contains(o.Id)).ToList();
return null;
I dislike the using 3 = some type example. It obfuscates for no real benefit, intelli-sense is sufficent that you can include the proper type name. Yes you save yourself a 3 or 4 keypresses but you do so at the expense of readability and maintainability. I guess it's fine if you the codebase is your own 1-person-band written but in a team I'd fail that in a code review.
Not new-new - but I love the Elvis operator.
return somethingThatCouldBeNull?.MethodReturningSomething().ToString() ?? string.Empty
instead of:
if (somethingThatCouldBeNull != null)
return somethingThatCouldBeNull.MethodReturningSomething().ToString()
else
return string.Empty
or I guess you could've had:
return (somethingThatCouldBeNull != null)
? somethingThatCouldBeNull.MethodReturningSomething().ToString()
: string.Empty
And if you want to cut down on the line count, replace
if (shows2Sponsors.TryGetValue(showId, out var map))
return sponsors.Where(o => map.Sponsors.Contains(o.Id)).ToList();
return null;
with
return shows2Sponsors.TryGetValue(showId, out var map) ? sponsors.Where(o => map.Sponsors.Contains(o.Id)).ToList() : new List<Sponsor>();
Or switch to APL :-)
use Lazy in ConcurrentDictionary instead of passing a Func to GetOrAdd
return TryUpdate(input, out _);
That you can give tuples names.
public (string Name, string Email) GetNameAndEmail()
{
...
}
// alt 1
var profile = GetNameAndEmail();
var x = profile.Name;
// alt 2
var (name, email) = GetNameAndEmail();
Наш сайт: https://lhqkrwmk.morningeverning.com
public ValueController(IMediator mediator) =>
_mediator = mediator:
Async console apps:
static async Task Main(string[] args)
var obj = object?.Property ?? value;
void DoSomething(IEnumerable<Object> objs)
{
if((objs?.Count() ?? 0) > 0)
{
//do something
}
}
Shows2Sponsor map = null;
shows2Sponsors.TryGetValue(showId, out map);
if (map != null)
{
var retVal = sponsors.Where(o => map.Sponsors.Contains(o.Id)).ToList();
return retVal;
}
return null;
and
if (shows2Sponsors.TryGetValue(showId, out var map))
return sponsors.Where(o => map.Sponsors.Contains(o.Id)).ToList();
return null;
Isn't really the same code. You just removed the null-check, which can be a valid value for a showId (assuming it's a collection of a kind) and the code may run on an NullReferenceException.
Also not sure you are doing yourself a favor using
using J = Newtonsoft.Json.JsonPropertyAttribute;
Which means I can do this:
[J("description")]
public string Description { get; set; }
[J("long_description")]
public string LongDescription { get; set; }
Shorter? Definitely. Readable? Nope.
using AsJson = Newtonsoft.Json.JsonPropertyAttribute;
Which means I can do this:
[AsJson("description")]
public string Description { get; set; }
[AsJson("long_description")]
public string LongDescription { get; set; }
would be better though.
Saving bytes/letter is a Ruby-type of programming. Bad :p
Comments are closed.