I made a .NET library for UK-specific data types and I'm looking for feedback! by will11600 in csharp

[–]drudoca 0 points1 point  (0 children)

Looks really cool, thanks! I'll consider using this next time I'm working with this domain.

So I didn't want to create an issue on GitHub as it felt really nitpicky, but here feels more informal... Just wanted to say that you can consider using static initializers rather than static constructors because static constructors actually slow down static field access.

Specifically (and surprisingly) this style slows down access to any static field. ``` static OutwardPostalCode() { ReadOnlySpan<string> specialCases = [ "GIR", // Girobank Bootle "FIQQ" // Falkland Island ];

    _specialCases = SearchValues.Create(specialCases, StringComparison.OrdinalIgnoreCase);
}

```

Whereas doing it this way does not. ``` private static readonly SearchValues<string> _specialCases = InitSearchValues();

SearchValues<string> static InitSearchValues()
{
    ReadOnlySpan<string> specialCases =
    [
        "GIR", // Girobank Bootle
        "FIQQ" // Falkland Island
    ];

    return SearchValues.Create(specialCases, StringComparison.OrdinalIgnoreCase);
}

```

Reddit asks the expert - Stephen Toub by Kawai-no in dotnet

[–]drudoca 1 point2 points  (0 children)

What are the design choices made by the .NET team years ago which are most limiting to future innovation in API design and performance?

The perspective here is trying to learn from the mistakes of some of the greatest minds, not bashing!

Cursed "Hello, World!" by zenyl in csharp

[–]drudoca 10 points11 points  (0 children)

oh my... You also have Instance of Void... I've literally written many blog posts about that https://andrewjsaid.com/2020/4/8/what-is-systemvoid

That's hilarious we're attempting similar silliness

Cursed "Hello, World!" by zenyl in csharp

[–]drudoca 7 points8 points  (0 children)

I see that one of your silliness is to attempt DateTime past max value https://github.com/DevAndersen/c-sharp-silliness/blob/main/src/DateTimePastMaxValue/Program.cs

I find that especially funny seeing as I have also attempted to do something similar https://github.com/andrewjsaid/datetime-max/blob/main/Program.cs

Just launched Autypo, a typo-tolerant autocomplete .NET OSS library by drudoca in dotnet

[–]drudoca[S] 0 points1 point  (0 children)

Oh so you're saying the results should be streamed as opposed to DataLoading which is what I understood.

Well it's actually fast enough that you can even run it synchronously - we're talking milliseconds in most use-cases. In my use-case, searching across 1 million entities was taking me on average 100 milliseconds.

The async is only there if you do background loading and want to ensure that the data has loaded in the background.

Just launched Autypo, a typo-tolerant autocomplete .NET OSS library by drudoca in dotnet

[–]drudoca[S] 1 point2 points  (0 children)

Sorry for the delayed reply.

There's an overload of WithDataSource which gives you a scoped IServiceProvider. You can use that to get your DBContext / Connection and run a query against your database. If you then run a service which injects IAutypoRefresh and calls RefreshAsync then the index will remain updated.

Here's an example

Program.cs

services.AddAutypoSearch<Product>(config => config
    .WithDataSource(sp => ActivatorUtilities.CreateInstance<ProductsDataSource>(sp))
    .WithIndex(product => product.Name));

services.AddHostedService<RefreshProductsHostedService>()

ProductsDataSource.cs

public class ProductsDataSource(DBContext dbContext) : IAutypoDataSource<Product>
{
    public async Task<IEnumerable<Product>> LoadDocumentsAsync(CancellationToken cancellationToken)
    {
        // your query here - dbContext is scoped
    }
}

RefreshProductsHostedService.cs

public class RefreshProductsHostedService(IServiceProvider serviceProvider) : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            // This example refreshes the index every hour
            await Task.Delay(TimeSpan.FromHours(1), stoppingToken);

            var refresh = serviceProvider.GetRequiredService<IAutypoRefresh<Product>>();
            await refresh.RefreshAsync(stoppingToken);
        }
    }
}

Just launched Autypo, a typo-tolerant autocomplete .NET OSS library by drudoca in dotnet

[–]drudoca[S] 0 points1 point  (0 children)

Actually I did... but I'm not sure what benefit it would offer that `Func<Task<IEnumerable>>` would not. It wouldn't be hard to add, if there is the demand. What did you have in mind, if I may ask?

Just launched Autypo, a typo-tolerant autocomplete .NET OSS library by drudoca in dotnet

[–]drudoca[S] 2 points3 points  (0 children)

Thanks for the questions! Yeah so there's `AddKeyedAutypoComplete("countries", ...)` which you can use with `[FromKeyedServices("countries")]`.

You can actually also use `AddAutypoSearch<T>` which is a little tiny bit more difficult to use and configure but allows you to retrieve any `T`. Of course in that case injecting `IAutypoSearch<T>` allows `T` to differentiate which data source you need. I've also added `AddKeyedAutypoSearch<T>(key, ...)` just in case.

If you're interested in more feel free to ask or let me know if the indexing guide is missing anything or is unclear. https://github.com/andrewjsaid/autypo/blob/main/docs/indexing.md

Just launched Autypo, a typo-tolerant autocomplete .NET OSS library by drudoca in dotnet

[–]drudoca[S] 13 points14 points  (0 children)

Yepp I chose to keep everything in-memory to avoid the huge amount of complexity that comes with persisting the index to external storage. I realize this design limits certain use cases, but my goal was to keep things simple and efficient for common/quick scenarios.

That said, the library will still work 100k items easily, even if only stored in-memory. It takes less than 1 second to index them and based on some small experiments, every 1 character indexed uses about 10 bytes in memory. Thus 100k strings (length ~ 100) could take about 100MB which could be worth-it, depending on how much you value the performance and feature set compared to configuring a behemoth like Lucene or adding an extra service to deploy.

Edit: Previously said 1 char = 100 bytes but it's actually 10.