dreadfullydistinct
dreadfullydistinct
CC#
Created by dreadfullydistinct on 4/7/2024 in #help
LoggerMessage organisation
Looking for some insight in how people using SG logging organise their methods. Do you have them inside the class?
public partial class Class(ILogger<Class> logger)
{
public void OpenSocket(string host)
{
SocketOpened(logger, host);
}

[LoggerMessage(Level = LogLevel.Information, Message = "Opened socket to `{HostName}`")]
private static partial void SocketOpened(ILogger logger, string hostName);
}
public partial class Class(ILogger<Class> logger)
{
public void OpenSocket(string host)
{
SocketOpened(logger, host);
}

[LoggerMessage(Level = LogLevel.Information, Message = "Opened socket to `{HostName}`")]
private static partial void SocketOpened(ILogger logger, string hostName);
}
disadvantages: have to make all classes that use logging partial (although maybe that isn't so bad), and also I like the ext method syntax more or do I just accumulate a static Log class for extension methods
public class Class(ILogger<Class> logger)
{
public void OpenSocket(string host)
{
logger.SocketOpened(host);
}
}

public static partial class Log
{
[LoggerMessage(Level = LogLevel.Information, Message = "Opened socket to `{HostName}`")]
public static partial void SocketOpened(this ILogger logger, string hostName);
}
public class Class(ILogger<Class> logger)
{
public void OpenSocket(string host)
{
logger.SocketOpened(host);
}
}

public static partial class Log
{
[LoggerMessage(Level = LogLevel.Information, Message = "Opened socket to `{HostName}`")]
public static partial void SocketOpened(this ILogger logger, string hostName);
}
I like the ext method here but there is no way to separate the logging methods and make them private, so I can see the code completion getting huge and naming collisions becoming a problem
11 replies
CC#
Created by dreadfullydistinct on 3/30/2024 in #help
✅ ɹoʇɐɹǝuǝƃ ǝɔɹnos lɐʇuǝɯǝɹɔuı uɐ ʇɐ ʎɹʇ ʇsɹıɟ
Had a go at writing an SG but I’m not very experienced with Roslyn so would appreciate some feedback. I’ve tried to apply the things I’ve overheard in the Roslyn file about enabling caching/incrementality but I’m not sure how to test it’s properly incremental. Main sgen project: https://github.com/SapiensAnatis/Dawnshard/tree/1c04fb2944fc69dc706eb02aa8ab96fbf8eaf7c0/DragaliaAPI/DragaliaAPI.Shared.SourceGenerator Sgen tests: https://github.com/SapiensAnatis/Dawnshard/tree/1c04fb2944fc69dc706eb02aa8ab96fbf8eaf7c0/DragaliaAPI/DragaliaAPI.Shared.SourceGenerator.Test The goal of the sgen is to generate some deserialisation code for a static class, which is here https://github.com/SapiensAnatis/Dawnshard/blob/1c04fb2944fc69dc706eb02aa8ab96fbf8eaf7c0/DragaliaAPI/DragaliaAPI.Shared/MasterAsset/MasterAsset.cs The code it actually generates is here: https://github.com/SapiensAnatis/Dawnshard/blob/1c04fb2944fc69dc706eb02aa8ab96fbf8eaf7c0/DragaliaAPI/DragaliaAPI.Shared/Generated/DragaliaAPI.Shared.SourceGenerator/DragaliaAPI.Shared.SourceGenerator.MasterAssetGenerator/MasterAsset.g.cs
10 replies
CC#
Created by dreadfullydistinct on 3/27/2024 in #help
✅ Writing a source generator for this use case
I have some JSON data I want to load at startup, but image size has become a concern since in the published image these files are taking up 80MB. The general pattern I am using at the minute is
public static class MasterAsset
{
public static MasterAssetData<Charas, CharaData> CharaData { get; private set; } = null!;

public static async Task LoadAsync()
{
ValueTask<MasterAssetData<Charas, CharaData>> charaData = MasterAssetData.LoadAsync<
Charas,
CharaData
>("CharaData.json", x => x.Id);

ValuteTask<MasterAssetData<Other, Other>> other = ...

CharaData = await charaData;
Other = await other;
}
}
public static class MasterAsset
{
public static MasterAssetData<Charas, CharaData> CharaData { get; private set; } = null!;

public static async Task LoadAsync()
{
ValueTask<MasterAssetData<Charas, CharaData>> charaData = MasterAssetData.LoadAsync<
Charas,
CharaData
>("CharaData.json", x => x.Id);

ValuteTask<MasterAssetData<Other, Other>> other = ...

CharaData = await charaData;
Other = await other;
}
}
Note that there are about 100 such properties and I am interleaving the async calls to load them in parallel. Here the MasterAssetData.LoadAsync factory takes the JSON file which is an array and processes it as a dict:
public static class MasterAssetData
{
public static async ValueTask<MasterAssetData<TKey, TItem>> LoadAsync<TKey, TItem>(
string path,
Func<TItem, TKey> keySelector
)
where TItem : class
where TKey : notnull
{
await using FileStream fs = File.OpenRead(path);

List<TItem> items =
await JsonSerializer.DeserializeAsync<List<TItem>>(fs);

FrozenDictionary<TKey, TItem> frozenDict = items
.ToDictionary(keySelector, x => x)
.ToFrozenDictionary();

return new MasterAssetData<TKey, TItem>(frozenDict);
}
}
public static class MasterAssetData
{
public static async ValueTask<MasterAssetData<TKey, TItem>> LoadAsync<TKey, TItem>(
string path,
Func<TItem, TKey> keySelector
)
where TItem : class
where TKey : notnull
{
await using FileStream fs = File.OpenRead(path);

List<TItem> items =
await JsonSerializer.DeserializeAsync<List<TItem>>(fs);

FrozenDictionary<TKey, TItem> frozenDict = items
.ToDictionary(keySelector, x => x)
.ToFrozenDictionary();

return new MasterAssetData<TKey, TItem>(frozenDict);
}
}
I wanted to write a console app to run at build time, to convert all the json files to MessagePack with LZ4 compression. I initially used Roslyn to attempt to parse the contents of the MasterAsset class as a standalone cs file (so no symbol info) to build an association between json filepath -> type name, e.g. CharaData.json -> CharaData, so that I could first load up the JSON to deserialize it before re-serializing as binary, but I ran into a number of stumbling blocks trying to go from that type name to an actual Type loaded via reflection, in the presence of things like generic types e.g. MasterAssetData<int, EventItem<BuildEventItem>>. I think it would be easier if I rethought this approach and wrote a source generator so that I had
[GenerateMasterAsset<Charas, CharaData>("CharaData.json", nameof(CharaData.Id))]
public static partial class MasterAsset
{
}
[GenerateMasterAsset<Charas, CharaData>("CharaData.json", nameof(CharaData.Id))]
public static partial class MasterAsset
{
}
which could then generate a property and LoadAsync method for you, like in my second code block. This would then allow me in my console app to get the json path via reflection of the metadata of MasterAsset rather than trying to parse the syntax tree for the function call, and it would save some typing when new entries are to be added. Downside is it feels a bit overkill to write an SG for just one class. I'd appreciate any thoughts, maybe the whole approach is horrific or maybe I'm on to something 🙂
23 replies
CC#
Created by dreadfullydistinct on 3/13/2024 in #help
Why is my SIMD code slower than the scalar version?
I wrote the following to learn more about simd - it tries to find a substring https://paste.mod.gg/jswpvcpgoxgo/0 I ran a benchmark on my machine, which is avx512, compared against when it goes down the scalar path by setting DOTNET_EnableHWIntrinsic=0. In my benchmark I have 2 paragraphs of Lorem Ipsum (1156 chars length) and a search string of a few words (47 chars length). The vector512 benchmark takes approx 2.8us and the scalar benchmark takes 4.1us which seems like a fairly large difference and indicative that I’ve done something wrong. Is there any more profiling I can use to work out what went wrong?
5 replies
CC#
Created by dreadfullydistinct on 3/7/2024 in #help
✅ Am I using NumberFormatInfo properly?
I have a list of ca. 400 numbers which I want to convert to percentage strings as part of an endpoint. I know I will only ever want two decimal places or three decimal places. I was wondering about the impact of allocating a new NumberFormatInfo each time so I whipped up some code:
[Benchmark]
public static string NoCache2()
{
return ToPercentageString(0.0433m, 2);
}
[Benchmark]
public static string NoCache3()
{
return ToPercentageString(0.0433m, 3);
}
[Benchmark]
public static string Cache2()
{
return ToPercentageString2Dp(0.0433m);
}
[Benchmark]
public static string Cache3()
{
return ToPercentageString3Dp(0.0433m);
}

public static string ToPercentageString(decimal d, int decimalPlaces)
=> d.ToString(
"P",
new NumberFormatInfo()
{
PercentDecimalDigits = decimalPlaces,
PercentPositivePattern = 1
}
);

public static string ToPercentageString2Dp(decimal d)
=> d.ToString("P", TwoDpFormat);

public static string ToPercentageString3Dp(decimal d)
=> d.ToString("P", ThreeDpFormat);

private static readonly NumberFormatInfo TwoDpFormat = new NumberFormatInfo()
{
PercentDecimalDigits = 2,
PercentPositivePattern = 1
};

private static readonly NumberFormatInfo ThreeDpFormat = new NumberFormatInfo()
{
PercentDecimalDigits = 3,
PercentPositivePattern = 1
};
[Benchmark]
public static string NoCache2()
{
return ToPercentageString(0.0433m, 2);
}
[Benchmark]
public static string NoCache3()
{
return ToPercentageString(0.0433m, 3);
}
[Benchmark]
public static string Cache2()
{
return ToPercentageString2Dp(0.0433m);
}
[Benchmark]
public static string Cache3()
{
return ToPercentageString3Dp(0.0433m);
}

public static string ToPercentageString(decimal d, int decimalPlaces)
=> d.ToString(
"P",
new NumberFormatInfo()
{
PercentDecimalDigits = decimalPlaces,
PercentPositivePattern = 1
}
);

public static string ToPercentageString2Dp(decimal d)
=> d.ToString("P", TwoDpFormat);

public static string ToPercentageString3Dp(decimal d)
=> d.ToString("P", ThreeDpFormat);

private static readonly NumberFormatInfo TwoDpFormat = new NumberFormatInfo()
{
PercentDecimalDigits = 2,
PercentPositivePattern = 1
};

private static readonly NumberFormatInfo ThreeDpFormat = new NumberFormatInfo()
{
PercentDecimalDigits = 3,
PercentPositivePattern = 1
};

The results indicate caching is better but the end result after all the numbers in terms of allocs or nanoseconds saved seems unlikely to be significant (probably 217kB memory saved, or 12 microseconds faster). Is this premature optimization? OR is there a more idiomatic way to use NumberFormatInfo that I'm missing here?
8 replies
CC#
Created by dreadfullydistinct on 2/25/2024 in #help
✅ Renaming members across a project
I had some generated classes that looked like this:
[MessagePackObject(true)]
public class AchievementList
{
public int achievement_id { get; set; }
public int progress { get; set; }
public int state { get; set; }
}
[MessagePackObject(true)]
public class AchievementList
{
public int achievement_id { get; set; }
public int progress { get; set; }
public int state { get; set; }
}
where the properties were named in snake_case, because the property name drove what was serialized. I've since decided to re-scaffold them using my script more like
[MessagePackObject]
public class AchievementList
{
[Key("achievement_id")]
public int AchievementId { get; set; }

[Key("progress")]
public int Progress { get; set; }

[Key("state")]
public int State { get; set; }
}
[MessagePackObject]
public class AchievementList
{
[Key("achievement_id")]
public int AchievementId { get; set; }

[Key("progress")]
public int Progress { get; set; }

[Key("state")]
public int State { get; set; }
}
but now obviously everywhere I've used this type won't compile. I now have approximately 8300 errors in my project and I think a simple find and replace won't save me - what are some options to fix the errors? I could possibly write a script to convert all snake_case names to pascal case?
10 replies
CC#
Created by dreadfullydistinct on 2/22/2024 in #help
✅ Is there a way you can check if an object’s equality operator will compare by reference?
I want to implement a data validation attribute like [UniqueValues] for ASP.NET model binding which will basically just call .Distinct() and check the count against the original list. This however won’t work with plain classes, only value types, records, or classes that implement IEquatable. Is there a way I can check an object instance to see if it will be compared by reference, and if so throw an exception?
13 replies
CC#
Created by dreadfullydistinct on 12/13/2023 in #help
✅ Can’t disable warnings in generated code
My warnings as errors pipeline is failing because of generated code ending with .g.cs which doesn’t have xml comments. I added the following to the editorconfig at the root of my solution:
[*.g.cs]
generated_code = true
[*.g.cs]
generated_code = true
But the warnings keep appearing. Is this not a valid selector for editorconfig?
35 replies
CC#
Created by dreadfullydistinct on 12/10/2023 in #help
MSBuild task that shouldn't run in CI
I've got an MSBuild task which I don't want to run in my pipelines. So I wrote the following:
<Target Name="MissionDesigner" BeforeTargets="AfterBuild" Condition="'$(GITHUB_ACTIONS)' == ''">
<Exec Command="dotnet run --no-dependencies --project ../DragaliaAPI.MissionDesigner -- ../DragaliaAPI.Shared/Resources/Missions/MissionProgressionInfo.json" ConsoleToMSBuild="true"/>
</Target>
<Target Name="MissionDesigner" BeforeTargets="AfterBuild" Condition="'$(GITHUB_ACTIONS)' == ''">
<Exec Command="dotnet run --no-dependencies --project ../DragaliaAPI.MissionDesigner -- ../DragaliaAPI.Shared/Resources/Missions/MissionProgressionInfo.json" ConsoleToMSBuild="true"/>
</Target>
because according to github, the GITHUB_ACTIONS env var should always be set by default in CI: https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables However, it still seems to run in my pipelines judging by the output. Am I doing something wrong?
2 replies
CC#
Created by dreadfullydistinct on 11/21/2023 in #help
Converting from a string to a generic method invocation
Looking for design patterns to help solve this problem I’m having at work. The situation is that we have a URL like GET /entities/<userid>/<entity type> where entity type is a string, and we want to fetch the entities of that type belonging to the current user. Normally I would just use a switch case to go from eg entitytype = “someentity” => entitiesService.GetEntities<SomeEntity>(). But my tech lead insists that new entity types should be automatically supported without code changes to this endpoint because he thinks it will be forgotten. I’ve landed on a solution using reflection to grab the types deriving from my base Entity class and build a dict of (string, Type), then using reflection to get the method info of GetEntities and use MakeGenericMethod with the type from the dict, but am not too pleased with it. I’m considering the following alternatives: - source generators, seemed like a lot of code to generate 1 switch case - using reflection in the testing to make the pipeline fail if another dev forgets to update the endpoint But I’d like to know if there are any other solutions people can think of / thoughts on some of the existing options
12 replies
CC#
Created by dreadfullydistinct on 10/7/2023 in #help
❔ NavigationManager: TaskCancelledException
I've got an OAuth login button:
@inject IOptionsMonitor<BaasOptions> Options;
@inject NavigationManager Navigation;

<MudButton OnClick="OnClickLogin" Variant="Variant.Text" Color="Color.Inherit">Login</MudButton>

@code {
[CascadingParameter(Name = "HttpRequestState")]
protected HttpRequestState? HttpRequestState { get; set; }

public void OnClickLogin()
{
ArgumentNullException.ThrowIfNull(HttpRequestState);

string currentPage = Navigation.ToBaseRelativePath(Navigation.Uri);
string redirect = HttpRequestState.HostUri + $"/OAuthCallback?{Constants.QueryParams.OriginalPage}={currentPage}";

QueryBuilder qb =
new()
{
// some query params for the request, omitted for brevity
};

Navigation.NavigateTo(new Uri(this.Options.CurrentValue.BaasUrlParsed, "/custom/thirdparty/auth") + qb.ToString());
}
}
@inject IOptionsMonitor<BaasOptions> Options;
@inject NavigationManager Navigation;

<MudButton OnClick="OnClickLogin" Variant="Variant.Text" Color="Color.Inherit">Login</MudButton>

@code {
[CascadingParameter(Name = "HttpRequestState")]
protected HttpRequestState? HttpRequestState { get; set; }

public void OnClickLogin()
{
ArgumentNullException.ThrowIfNull(HttpRequestState);

string currentPage = Navigation.ToBaseRelativePath(Navigation.Uri);
string redirect = HttpRequestState.HostUri + $"/OAuthCallback?{Constants.QueryParams.OriginalPage}={currentPage}";

QueryBuilder qb =
new()
{
// some query params for the request, omitted for brevity
};

Navigation.NavigateTo(new Uri(this.Options.CurrentValue.BaasUrlParsed, "/custom/thirdparty/auth") + qb.ToString());
}
}
And it seems I get quite a few errors from the deployment: Navigation failed when changing the location to https://baas.lukefz.xyz/custom/thirdparty/auth[...] with an inner exception of
System.Threading.Tasks.TaskCanceledException: A task was canceled.
at Microsoft.JSInterop.JSRuntime.InvokeAsync[TValue](Int64 targetInstanceId, String identifier, Object[] args)
at Microsoft.JSInterop.JSRuntimeExtensions.InvokeVoidAsync(IJSRuntime jsRuntime, String identifier, Object[] args)
at Microsoft.AspNetCore.Components.Server.Circuits.RemoteNavigationManager.<>c__DisplayClass13_0.<<NavigateToCore>g__PerformNavigationAsync|0>d.MoveNext()
System.Threading.Tasks.TaskCanceledException: A task was canceled.
at Microsoft.JSInterop.JSRuntime.InvokeAsync[TValue](Int64 targetInstanceId, String identifier, Object[] args)
at Microsoft.JSInterop.JSRuntimeExtensions.InvokeVoidAsync(IJSRuntime jsRuntime, String identifier, Object[] args)
at Microsoft.AspNetCore.Components.Server.Circuits.RemoteNavigationManager.<>c__DisplayClass13_0.<<NavigateToCore>g__PerformNavigationAsync|0>d.MoveNext()
I found a github issue with something similar which I now can't find again, but the root cause there was using Task.Result. However I am not using that anywhere so I'm not sure what is happening here, any suggestions would be appreciated
11 replies
CC#
Created by dreadfullydistinct on 8/21/2023 in #help
❔ Suppressing ASP0001: AuthorizationMiddleware is incorrectly configured
36 replies
CC#
Created by dreadfullydistinct on 1/16/2023 in #help
❔ ASP.NET Core Persist Task between requests
I am programming an API and one of my endpoints needs to do a database operation, but it will take a long time, and the result is not actually needed until a different endpoint is called. I would like to queue the Task on the first endpoint and return a 202 Accepted, then only have to await the task (if it has not already completed) on the other endpoint. I’m not sure what the best way to do this would be. I could have a static dictionary of tasks keyed by users stored in a service, but this seems naive. I’ve heard about background workers but this is an on-demand thing so I’m not sure if that’s appropriate either as I get the feeling they’re for cron-type things. Any advice would be appreciated.
195 replies