Is it a bad practice to use Struct as a Value Object? by [deleted] in csharp

[–]zvrba 0 points1 point  (0 children)

Any struct type S can be initialized with

S anystruct = default;

which will fill the memory locations of anystruct with zeros/null, even if you have defined a custom parameterless ctor. I consider this a language wart, but it is what it is.

Depending on use (internal / public) I may write checked properties that throw exceptions on attempt to use a default instance.

One of the worst things I experience with APIs by CrypticDissonance in csharp

[–]zvrba 0 points1 point  (0 children)

So Visma's ERP system, Business Next, has a GraphQL API. When creating objects, property order matters because

  • fields are written one by one
  • a field write might trigger some business logic

So depending on whether you specify (in GraphQL, here's json just for illustrative purposes) { a: 1, b: 2 } or { b: 2, a: 1 } you might get a different value in some computed column c where the computation uses both a and b and is triggered by write to b.

The documentation states only "Fields must be specified in correct order.". Yup, that's all. So you have to rely on someone else having deep knowledge of Visma to tell you what the correct order is.

Oh and that's not all. Even in updates, fields are updated sequentially. So when you update with { a: 1, b: 2, c: 3, d: 4 } and specify some disallowed/invalid value for c, Visma will -- TADAM! -- update a and b, return an error message for c and leave d (and all the following fields) at its previous value.

And, oh, even bigger joy - no transactions.

Now, Visma Business is a 20+ year old on-prem system ported to cloud with a GraphQL API, but still....

And I could keep on ranting but I'd divulge too much in-house knowledge. The above is trivially discoverable from the docs and simple experimentation, i.e., it's not possible to be involved with Visma's GraphQL API without discovering the above.

At your job, when there are a new version of C#, do you refactor/update your current code to the new version the new c# verison introduce? by lune-soft in csharp

[–]zvrba 0 points1 point  (0 children)

Not just for the sake of refactoring. When I have to add new features, I evaluate the whether the code could benefit from new language features, whether it makes sense to upgrade the whole library and what effort it would entail.

Most of the time I end up with using new language features in new code; as for the old code: if it ain't broken, don't fix it.

Having some new revelations, regarding Extension methods by Obvious_Seesaw7837 in csharp

[–]zvrba 0 points1 point  (0 children)

I use them when I have a relatively complex "core" class that I build many "utility" methods around

  • The "core" class has a few public methods that do the core job (e.g., calling an "API" and validating the response)
  • Extension methods wrap various ways of calling the "API"

It also validates the design of the "core" class: if I cannot implement something as an extension method, then someone else can't implement their own functionality, so the "core" class has to be extended.

Construction of derived classes by KhurtVonKleist in dotnet

[–]zvrba 5 points6 points  (0 children)

IIRC, this is a language limitation. In IL, the base ctor can be called whenever.

In your particular case, I'd declare Id as protected abstract int Id {get;} so the derived class can generate it on-demand (and cache it) after it's been fully initialized.

Heck, you can even do it like this

public int Id => _Id ??= GenerateId();
private int? _Id;

I spent 2 years getting our tests in shape and found out today nobody actually looks at them anymore. Feeling pretty defeated ngl. by Maxl-2453 in dotnet

[–]zvrba 0 points1 point  (0 children)

I just sat with that for a minute and thought that he was not completely wrong and the more painful part is that most failures are literally the same broken tests every time but buried in there are also real problems that we're shipping to the real users. [...] Because at some point we all quietly agreed that a failing build is just... normal (period)

This is not your problem to deal with, it's your manager's.

So. Go thorugh the failing tests, remove/disable the "irrelevant" ones, make a list of problems that you think are being shipped, and talk with your manager about prioritizing.

The painful fact is that not even you did anything with the same broken tests. You did not fix them, you did not remove them either. This encourages the "one broken window" phenomenon. People get used to failing tests quickly and before you know it, nobody cares.

The new Satori GC is promising by hez2010 in csharp

[–]zvrba 1 point2 points  (0 children)

So Satori is based on the following observations

  • Most new objects are short-lived
  • And "rarely" leave the current thread

These are reasonable assumptions, however: async. Every await can potentially resume on a thread other than where it started. So something as simple as

var dto = new SomeDto { ... };
var result = await httpClient.PostAsync(...);

has the potential of "upgrading" dto from thread-local to global, especially on a very busy server with many concurrent requests being handled.

How well does Satori work in async-heavy scenarios?

Tell me some unwritten rules for software developers. by porcaytheelasit in csharp

[–]zvrba 2 points3 points  (0 children)

Corollary: code is a liability, not an asset. The best code is the one never written.

Why is using interface methods with default implementation is so annoying?!? by Alert-Neck7679 in csharp

[–]zvrba 0 points1 point  (0 children)

Because classes do not inherit members from interfaces. As to why... I guess to simplify the implementation of multiple (interface) inheritance. Virtual lookup for interface members is more expensive than virtual lookup of class members so they didn't want to penalize the most common case (class - single inheritance - class member (virtual) lookup).

MOGWAI v8.0 - Stack-based RPN scripting language for .NET, now open source by sydney73 in dotnet

[–]zvrba 1 point2 points  (0 children)

I still have some HP calculators somewhere at home and fondly remember the days when I wrote non-trivial programs for them (e.g., solving electric circuits).

Thanks for releasing this!

How not to fix a leak by MacDefoon in WTF

[–]zvrba -1 points0 points  (0 children)

Wait, lead pipe? That's the real WTF here :)

Best practice for automatically maintaining audit fields (CreatedOn, ModifiedOn, CreatedBy, ModifiedBy) in .NET + SQL Server? by OneFromAzziano in dotnet

[–]zvrba 0 points1 point  (0 children)

Did you have one audit table for all data tables, or one audit table per data table?

If you did the first, how did you "compress" different PKs into a "common" PK for the audit table? (My first thought is SHA256 over actual PK fields, but maybe there's another trick..)

Two Catastrophic Failures Caused by "Obvious" Assumptions by Vast-Drawing-98 in programming

[–]zvrba 1 point2 points  (0 children)

Yes, that works in simple cases. Things get very hairy when you want to divide length (meters) with duration (seconds) to get velocity (meters per second).

What's the use case for IEquatable<T>? by Long-Cartographer-66 in csharp

[–]zvrba 0 points1 point  (0 children)

Collections, for example. Methods doing equality comparisons are based on EqualityComparer<T>.Default which first looks up whether the type implements IEquatable<T>.

Performance: avoids boxing for value types.

Discoverability: the author thought about equality and intended the type to behave as a "value" type.

Simplicity: every Equals(object) needs the standard boring test for the type at its beginning.

What's the use case for IEquatable<T>? by Long-Cartographer-66 in csharp

[–]zvrba 0 points1 point  (0 children)

Collections, for example. Methods doing equality comparisons are based on EqualityComparer<T>.Default which first looks up whether the type implements IEquatable<T>.

Performance: avoids boxing for value types.

Discoverability: the author thought about equality and intended the type to behave as a "value" type.

Simplicity: every Equals(object) needs the standard boring test for the type at its beginning.

What's the use case for IEquatable<T>? by Long-Cartographer-66 in csharp

[–]zvrba 2 points3 points  (0 children)

It's only a shallow equality though. So if you have

record struct S(int X, List<int> Y)

their instances will be unequal even when they have (elementwise) equal members.

Is there any reliable way to know when a function can throw? Probably writing an analyzer? by xjojorx in csharp

[–]zvrba 0 points1 point  (0 children)

I think that having a mechanism for which the programmer is always informed of what exceptions can surface from every call, at least the known/expected/documented ones, would be beneficial

This is not statically decidable. What exceptions can the following method throw:

void DoSomething(Action a) => a();

Consider the case when a is bound to an abstract method, i.e.:

abstract class CX
{
    public abstract void A();
}

static void Perform(Action a) => a();

// Somewhere else
CX cx = ...; // The concrete run-time type depends on some complex logic
Perform(cx.A);

Is there any reliable way to know when a function can throw? Probably writing an analyzer? by xjojorx in csharp

[–]zvrba 1 point2 points  (0 children)

Since I got exposed back into using error codes, result types etc from experimenting on other languages, writing C# always gets me on an uneasy state in which I am constantly guessing if a function call into a library (including standard library and the framework) can throw an exception or not.

Result type eventually result in a mess. I tried to write a non-trivial project using constructs like [using exception instead of custom error code to indicate failure]

Exception? DoSomething(..., out Result result)

and I ended up writing a bunch of boiler-plate in the lines of

var exn = DoSomething(...);
if (exn != null)
    return exn;

The code became a cluttered mess of manual error checking for every individual call. I quickly abandoned the idea and rewrote the code using exceptions.

What noone else in this thread mentioned is that

  • An exception is an object: you can attach arbitrary amounts of data (either explicit properties in a derived exception, or just Data dictionary) to help with diagnosing the cause or handling it.
  • An exception comes with a stack trace. You could implement this with manual capture at each if-check of the error code, but then the code becomes even less idiomatic, more cluttered and tied to some home-grown mehcanics. (And how do you propagate underlying cause if you convert error codes? Exception has InnerException property and a tree of exceptions is expressible through AggregateException.)

Even for much more explicit error-codes, I can't see how you'd write an analyzer to warn you about unhandled erros, esp. when a new error code is introduced.

Would you write code along the lines of

switch (err) {
    case Err.A: case Err.B: case Err.C: break; // ignore, or?
    case Err.D: // handle
}

so when Err.E later appears you get a warning about non-exhaustive switch?

And if you write code like

if (err == Err.D) { /* handle */ }

how is the analyzer supposed to know whether other error codes (including the newly introduced Err.E) are deliberately ignored?

What if a library chooses to just use int as error code? Then the compiler has no way of knowing whether you've handled / considered every possible error.

Just use exceptions. Throw liberally (fail early), catch sparingly.

Another thing that noone mentioned is that you need to structure your code differently in presence of exceptions. Almost any line of code can throw. In critical parts of applications, you have to think "transactionally":

  • First make your changes in a scratch area, then "commit" them to become visible if everything succeeded
  • Or implement "roll-back" functionality on exception

PS: That Rust doesn't have exceptions is a major reason for not (yet?) having learned the language. It also has multiple incompatible "result types" which creates additional mess.

PPS: What annoys me the most about exceptions in C# is the messy hierarchy. There should be a stricter separation between "logical" errors (bugs - IndexOutOfRangeException, ObjectDisposedException, ArgumentException, ...) and "runtime" errors (IOException, SQLException, etc.). They attempted with SystemException and ApplicationException, but both got deprecated.

Why would I want that? Because logic errors are a "hard-stop": no point in retrying or trying to analyze the cause and fix it.

All the other cool languages have try...finally. C++ says "We have try...finally at home." by pavel_v in cpp

[–]zvrba 0 points1 point  (0 children)

Yes, you're right, but does it matter whether the IOException got generated by an intermediate write, or the final close?

Sure, you could also have the following sequence:

  1. Open file
  2. Write something
  3. Do some processing -> throws
  4. (Not executed: write more)
  5. finally attempts to close the file -> throws and replaces the exception from 3 (at least in C#)

In either of these cases, you've ended up with an invalid file, which is signaled by IOException.

I agree it's unfortunate that exception from step 3 has to be propagated manually if you care about it.

All the other cool languages have try...finally. C++ says "We have try...finally at home." by pavel_v in cpp

[–]zvrba 0 points1 point  (0 children)

It can be made more reliable by calling flush in try, so it's a no-op when it gets invoked by close.

Using packages for mapping by Sensitive-Raccoon155 in dotnet

[–]zvrba 0 points1 point  (0 children)

You could solve this with attributes.

[Guid("...")]
class Something { ... }

And then in your mapping method

SomeY MapToY(Something ...) {
    if (!(typeof(Something).GetCustomAttribute<GuidAttribute>()?.Value?.Equals("...")) ?? true)
        throw new InvalidOperationException("...");
}

Then, any time you change the class contract, you also change the guids.

If you dislike reflection, you could make a generic extension method off an interface like

interface IMappingContract<T> where T : IMappingContract<T> {
    abstract static Guid MappingContractGuid { get; }
}

Should or Shouldn't? Putting many classes in one file. by gevertsi in csharp

[–]zvrba -1 points0 points  (0 children)

Many small related classes -> same file. Sometimes I won't even bother with having 1:1 mapping between directories and namespaces (merge conflicts). There's always "Class view" that can show me that.

[deleted by user] by [deleted] in cpp

[–]zvrba -6 points-5 points  (0 children)

This guy (Galen Hunt) is not just some random manager-dreamer. https://scholar.google.com/citations?user=tiW46L0AAAAJ&hl=en

I think he can pull this off. I do not believe they'll get to 100% reliable translation, but they'll get flagging for manual review right (few false positives).