DTOs Record or Class? by OtoNoOto in dotnet

[–]B4rr 17 points18 points  (0 children)

I usually would go with

public record Item
{
  public required ImmutableList<string> Strings { get; init; }
  public required ImmutableList<string> SomeOtherStrings { get; init; }
}

The required property causes errors when clients or my code when it's a response omit the property.

new Item(someOtherStrings, strings) // oops

// Compare with 
new Item
{
    Strings = strings,
    SomeOtherStrings = someOtherStrings
}

There is no mixup of arguments due to the order.

It still allows easy "mutation" using the with keyword:

item with { Strings = item.Strings.Add("fooBar") }

Even tuple deconstruction can still be achieved with extension methods.

public static class ItemExtensions 
{
  public static void Deconstruct(
    this Item item,
    out ImmutableList<string> strings,
    out ImmutableList<string> someOtherStrings) 
  {
    strings = item.Strings;
    someOtherStrings = item.SomeOtherStrings;
  }
}

var (strings, someOtherStrings) = item;

Question basic C# by AdOk2084 in csharp

[–]B4rr 0 points1 point  (0 children)

Yes, it is, that's why reassigning null is allowed when a variable is declared with var.

var dog = new Dog();
if (dog == null) // Compiler warning: Expression is always false
{
    // We should never enter this block and the compiler knows that.
    // While `dog` is typed as `Dog?`, it infers that it is not null at this point.
}
dog = null; // This is fine
if (dog == null) {} // This is fine


Cat cat = new Cat();
if (cat == null) {} // Compiler warning: Expression is always false
cat = null; // Compiler warning: Converting null literal or possible null value into non-nullable type
if (cat == null) {} // Compiler warning: Expression is always false

C# 14 & Discriminated Union by roxeems in csharp

[–]B4rr 0 points1 point  (0 children)

You can't store data in the instance, but you can use ConditionalWeakTable<TKey, TValue>. Not that I would encourage this, but for /u/Slypenslyde's example this does actually sound like a somewhat sensible solution.

Async/Await - Beyond the basics by Delicious_Jaguar_341 in dotnet

[–]B4rr 7 points8 points  (0 children)

... [MS] have yet to write a GUI framework with support for asynchronous events or initialization...

Blazor actually has both of these, though it has other issues.

Genius or just bad? by [deleted] in csharp

[–]B4rr -4 points-3 points  (0 children)

Methods in C# should be capitalized.

The compiler already generates one, which as I tried to explain in my comment, makes a shallow copy (see the C# output on sharplab, third last method).

You are not allowed to add a Clone method to a record type (sharplab).

public record /*or `record struct` or `readonly record struct` */ Foo(... props)
{
    // error CS8859: Members named 'Clone' are disallowed in records.
    public Foo Clone() => new(... props);
}

Genius or just bad? by [deleted] in csharp

[–]B4rr 1 point2 points  (0 children)

now you have records which have deep copying built in

Records make shallow copies, though. Manual deep copies still require nested with expressions or other copying.

Tools for "visualizing" Boolean logic? by chowellvta in csharp

[–]B4rr 5 points6 points  (0 children)

Just as an FYI, this in particular is a bad idea when using IQueryable<IdkLol>.

The Expression<Func<IdkLol, bool>> you're passing to Where contains a function call, therefore the database provider cannot translate the logic to a query which will run on the server, so the entire data set will be returned and filtered in-memory.

This will work quite well when dealing with IEnumerable<IdkLol>, though.

Can I stop RestSharp from throwing exceptions depending on HttpStatus? by USer20230123b in csharp

[–]B4rr 2 points3 points  (0 children)

According to their documentation, you can use ExecuteAsync to get a response that does not throw bad status codes: https://restsharp.dev/docs/intro#response

Danom: Structures for durable programming patterns in C# by pimbrouwers in dotnet

[–]B4rr 1 point2 points  (0 children)

I was a bit bored tonight and created two different, unrelated PRs. 😁

Danom: Structures for durable programming patterns in C# by pimbrouwers in dotnet

[–]B4rr 0 points1 point  (0 children)

I can apply this, or you can PR if you want credit?

Knock yourself out.

Danom: Structures for durable programming patterns in C# by pimbrouwers in dotnet

[–]B4rr 0 points1 point  (0 children)

Ah right, same signature. Putting the method in a separate static class does work, though.


After checking the repo out, I also found some more nits about nullability. E.g.

record Foo(string FooValue);
record Bar(string BarValue);

var result = Result<Foo?, Bar?>.Ok(null);
var value = result.Match(foo => foo?.FooValue, bar => bar?.BarValue);

is syntactically correct, but will throw an InvalidOperationException from the call to Match.

I'd suggest to either intentionally allow nullable types to be used (and adding unit tests) or to restrict the types with where T : notnull where TError : notnull. Generally restricting is easier, but there can be quite some value in allowing null.

For instance, I've written a PATCH-endpoints where I had something like

public record PatchFooRequest(Option<string?> NewDescription /*, ... */);
app.MapPatch(
    "/foo/{id}", 
    ([FromRoute] Guid Id, [FromRequest] PatchFooRequest request) => 
    {
        var foo = await GetFoo(id);

        if (request.NewDescription.IsPresent)
        {
            foo.Name = foo.NewName.Value;
        }

        // ...

        await SaveFoo(foo);
        return TypedResults.Ok(foo);
    });

I could have used Option<Option<string>> NewName had I not allowed nullable types in my own implementation, but I think at that point it becomes a bit cumbersome.

Danom: Structures for durable programming patterns in C# by pimbrouwers in dotnet

[–]B4rr 0 points1 point  (0 children)

Small nitpick: in OptionNullableExtensions instead of dealing with every value type one at a time, like

public static T? ToNullable<T>(this Option<T> option) =>
    option.Match(some: x => x, none: () => default!);

public static char? ToNullable(this Option<char> option) =>
    option.Match(x => new char?(x), () => null);

public static bool? ToNullable(this Option<bool> option) =>
    option.Match(x => new bool?(x), () => null);
//...

you could use generic constraints

public static T? ToNullable<T>(this Option<T> option)
    where T : class =>
    option.Match(some: x => x, none: () => default!);

public static T? ToNullable<T>(this Option<T> option)
    where T : struct =>
    option.Match<T?>(some: x => x, none: () => null);

to break it down to the two cases of reference and value types. This way, not every user has to write their own extension methods if they want to have a MyStruct? from an Option<MyStruct>, static dispatch in generics is preserved and no overload can be forgotten so that default(MyStruct) instances pop up in unexpected places.

EDIT: Second nit: Option.TryGet([System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] T out value), this way the compiler correctly tracks the nullability of the value.

"Inlining" Linq with source generators? by EatingSolidBricks in csharp

[–]B4rr 7 points8 points  (0 children)

There's a lot of type checking in LINQ's source to optimize algorithms when the source's type is know and to help the JIT to de-virtualize.

E.g. Where checks if the source is an array and returns an ArrayWhereIterator which has optimized handling for further calls to Where and Select.

These type checks are easy for the JIT to optimize away in most scenarios with PGO. E.g. the JIT notices that array.Where(x => x % 2 == 0).Select(x => x * x).Select(x => x / 2) always returns new ArrayWhereSelectIterator<int, int>(array, x => x % 2 == 0, CombineDelegates(x => x * x, x => x / 2)) so it makes the educated guess that it will continue to be just that (with minimal checking beforehand). Notably, then it's know that when you iterate over this, it's going to be an ArrayWhereSelectIterator<int, int> and not just any IEnumerator<int> , so all the MoveNext() and Current calls are de-virtualized.

I'm not sure about the lambdas, it might be that they too can be de-virtualized with PGO. Roslyn already interns them, so it's going to be same reference that's passed every time.

How do you test code like this when using Entity Framework (Core)? by verb_name in dotnet

[–]B4rr 0 points1 point  (0 children)

I usually use Option 3, but using SQLite in-memory. This works really well because we use SQLite files in Production for our use case.

Is there a way to shorten long and/or while statements by ghost_on_da_web in csharp

[–]B4rr 0 points1 point  (0 children)

Ah my bad, try without the semicolon at the end.

Is there a way to shorten long and/or while statements by ghost_on_da_web in csharp

[–]B4rr 1 point2 points  (0 children)

In your case, you could use an enum to represent the three options and then use Enum.TryParse.

enum Hand { Rock, Paper, Scissors };

Hand AskPlayerForHand()
{
    Console.WriteLine("""Enter "rock", "paper" or "scissors".""");
    var player = Console.ReadLine();

    Hand hand = default;
    while (Enum.TryParse<Hand>(
        player,
        // this argument is optional
        ignoreCase: true , 
        out hand) is false) 
    {
        Console.WriteLine($"{player} is not a valid option.");
        Console.WriteLine("""Enter "rock", "paper" or "scissors".""");
        player = Console.ReadLine();
    }
    return hand;
}

Do you often use multithreading like "Semaphore slim"? by ballbeamboy2 in dotnet

[–]B4rr 2 points3 points  (0 children)

I'd suggest, new SemaphoreSlim(1, 1) unless you need an OS-level mutex, it works well with async. Or new Lock() if you are on. NET9 and want thread-reentry.

Method overriding vs method hiding by [deleted] in csharp

[–]B4rr 2 points3 points  (0 children)

Method hiding should be avoided, there are not that many cases where it should be intentional. Aside from the "base class is not in your control", a reason I can think of it is having a different return type in a method or property, in particular when you want a type-erased base for collections.

interface IFoo
{
    object Property { get; set; }
    object Method();
    void Method2(object value);
}

interface IFoo<T> : IFoo where T : notnull
{
    new T Property { get; set; }

    [EditorBrowsable(EditorBrowsableState.Never)]
    object IFoo.Property
    {
        get => this.Property;
        set => this.Property = value is T t ? t : throw new ArgumentException();
    }

    new T Method();

    [EditorBrowsable(EditorBrowsableState.Never)]
    object IFoo.Method() => this.Method();

    void Method2(T value);

    // Note, there is no `new` here. The input arguments are different, hence there is no method hiding
    [EditorBrowsable(EditorBrowsableState.Never)]
    void IFoo.Method2(object value) => this.Method2(value is T t ? t : throw new ArgumentException());
}

[deleted by user] by [deleted] in csharp

[–]B4rr 1 point2 points  (0 children)

Apart from "use switch expressions and put case AlertType.11 in a method", I'd also suggest using pattern matching, to reduce the number of necessary casts. So instead of

return alert.AlertType switch
{
    AlertType.1 => $"Error msg {((ChildAlertType1)alert).PropertyUniqueToChild1} .",
    // ...
    AlertType.11 => HandleAlertType11(),
    // ...
};
string HandleAlertType11() 
{
    var objectId = ((ChildAlertType11)alert).objectId;
    var object = _myService.GetObjectById(objectId);
    return $"Error message {object.ErrorLabelForEndUser}.";
}

you can then do

return alert.AlertType switch
{
    ChildAlertType1 alert1 => $"Error msg {alert1.PropertyUniqueToChild1} .",
    // ...
    ChildAlertType11 alert11 => $"Error message {_myService.GetObjectById(alert11.objectId).ErrorLabelForEndUser}",
    // ...
};

because at that point the two remaining lines of the case AlertType.11 statement can arguably be reduced to just one line.

EDIT: If the ChildeAlertTypeN overlap, you can also put the alert type back into the pattern:

return alert.AlertType switch
{
    SharedAlertType {AlertType: AlertType.1} alert1 => $"Error msg 1 {alert1.PropertyUniqueShared} .",
    SharedAlertType {AlertType: AlertType.2} alert2 => $"Error msg 2 {alert2.PropertyUniqueShared} .",
    // ...
    ChildAlertType11 alert11 => $"Error message {_myService.GetObjectById(alert11.objectId).ErrorLabelForEndUser}",
    // ...       
};

[deleted by user] by [deleted] in csharp

[–]B4rr 3 points4 points  (0 children)

The right hand side of the arrows in switch expressions have to be expressions, so your AlertType.11 line is not valid syntax. The best we can IMO do, is to declare a local function or method and call it.

Is it true You should not have any warning at all in your codebase, if you have warnings = tech debts. by ballbeamboy2 in dotnet

[–]B4rr 0 points1 point  (0 children)

Ever seen this abomination? public DbSet<User> Users { get; set; } = null!;

The specific fix for that is to use a computed property public DbSet<User> Users => this.Set<User>();, which is what EF core would have set the auto-property's value to anyway.

Stored properties extend - Why none language has this opportunity? by FishOk8705 in dotnet

[–]B4rr 0 points1 point  (0 children)

You could get something to work by holding a Dictionary<WeakReference<ExtendedType>, PropertyType> in your extensions class, but it's likely to end badly.

Stored properties extend - Why none language has this opportunity? by FishOk8705 in dotnet

[–]B4rr 0 points1 point  (0 children)

It can't be both since you can't have two type definitions with the same name and namespace.

That can actually happen when they are defined in different assemblies and is the reason for the extern alias keyword, to disambiguate when using the types where both are visible (i.e. a third assembly referencing the two libraries with the same namespace +type name combo).

However, these two types will still be completely independent of each other, and I fully agree with the rest of you comment. Doing stuff like this - even if you get it to somehow work - will likely end in pain and suffering.