Sharpener.NamedResources
1.0.13
dotnet add package Sharpener.NamedResources --version 1.0.13
NuGet\Install-Package Sharpener.NamedResources -Version 1.0.13
<PackageReference Include="Sharpener.NamedResources" Version="1.0.13" />
<PackageVersion Include="Sharpener.NamedResources" Version="1.0.13" />
<PackageReference Include="Sharpener.NamedResources" />
paket add Sharpener.NamedResources --version 1.0.13
#r "nuget: Sharpener.NamedResources, 1.0.13"
#:package Sharpener.NamedResources@1.0.13
#addin nuget:?package=Sharpener.NamedResources&version=1.0.13
#tool nuget:?package=Sharpener.NamedResources&version=1.0.13
Named Resources
To understand named resources, you should know the dilemma. And that comes from a niche application downstream.
The challenge
I work on solutions that utilize dependencies that are not always built from C#. Often C++. And so you don't get the common advantages of the framework for resilient code, like composition with interfaces, or generic types. Whether it's possible in C++ is not the point, it's that the immutable dependencies I work with don't.
There are reference types that can have a wide variety of members. For example, a geometric object with dimensions. Depending on the predetermined shape type, it could have a diameter, width, height, depth, length, angle, and so on. And it can have any combination of those.
The dependency allows me to write code like this:
extension(GeometryItem item)
{
public double? GetDimension(string dimensionName)
{
// Often need to wrap in try catch because the interop can randomly cause data access failures that you
// cannot predict
try
{
var dimension = item.Dimensions.FirstOrDefault(dimension => dimension.Name.Equals(dimensionName, StringComparison.OrdinalIgnoreCase));
if (dimension?.IsValidObject == true && dimension.StorageType == StorageType.Double || dimension.Value is double doubleValue) return doubleValue;
}
catch
{
// Track the failure, maybe.
}
return null;
}
}
However, even after writing tooling like this, a larger application development efforts starts to really challenge you to avoid creating tech debt. Riddled throughout your application you'll start to see a whole lot of these:
var topExtension = item.GetDimension("Top Extension");
if(topExtension is null) return;
// or
var flatTop = item.GetOptionValue("Flat Top");
And this just gets repeated all over the place. Some of the option names are also quirky, like "Btm Width" instead of explicit "Bottom", or the regional dialect comes into play because someone from the UK built the dependency so it's a "Mitred" option for a corner instead of "Mitered".
You just want to define these string values once and be done with it! Ok, so what's wrong with this?
public static class Dimensions
{
// document everything too dude
public const string TopExtension = "Top Extension"
}
So I can write
var topExtension = item.GetDimension(Dimensions.TopExtension)
This is starting to look better, but it's more text to achieve a static value. Right? I mean, this is solid enough that, given no other options, I'd say this works. But I'm making a package here and that package aims to just improve life by that little bit.
So what does this package do?
The Solution
Start off by creating an enum with the values that you want to be able to lookup by.
/// <summary>
/// The common dimensions found on items.
/// </summary>
public enum DimensionType
{
/// <summary>
/// The width dimension of an item.
/// </summary>
[Description(nameof(Width))]
Width,
/// <summary>
/// The top extension dimension of an item.
/// </summary>
[Description("Top Extension")]
TopExtension
}
Then create an interface that inherits INamedResource.
public interface IDimensionName
{
}
Now decorate DimensionType to indicate that you want it to generate code for you to use, based on the enum.
/// <summary>
/// The common dimensions found on items.
/// </summary>
[NamedResource(typeof(IDimensionName))]
public enum DimensionType
Now you do not need to create that static class with the strings in it.
Dimensions.TopExtension was created for you.
But something else was created for you too, and that is a static type. It is meant for making some more advanced signatures.
var topExtension = new TopExtension();
// This comes out as "Top Extension"
topExtension.Name;
Well, what am I gonna do with that?
extension(GeometryItem item)
{
public double? Dim<TDim>() where TDim : IDimensionName, new() => item.GetDimension(new TDim().Name) ?? double.NaN;
public double? GetDimension(string dimensionName)
{
///
}
}
And so now, my typing can be a little more succinct and strong.
var widthIsLarge = item.Dim<Width>() > 12;
// or
var isFlatTop = item.Option<OffsetDepth>() == Choices.FlatTop;
And what's nice is that the consistency between these two values
Dimensions.FlatTop
// And
new FlatTop().Name
Will be predictable and reliable, because they are sourced by the enum that you maintain, the descriptions you put on them, and the summaries you provide in the xml documentation.
Overrides
You can override the defaults as well.
The constant strings are written to Dimensions because it removes "Name" from IDimensionName end, then if there are
two upper case letters in its typename, it removes the first, and lastly, it adds an "s" to the end.
[NamedResource(typeof(IDimensionName))]
// results in
Dims.Width == "Width"
The other optional attribute parameters govern the namespace that the generated values are placed in, which defaults to
the same namespace as IDimensionName, and you can override the intellisense documentation for the Dimensions class
output.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- Sharpener (>= 1.0.13)
- System.Memory (>= 4.6.3)
- System.Text.Json (>= 9.0.10)
-
net8.0
- Sharpener (>= 1.0.13)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.