Question Details

No question body available.

Tags

c# oop

Answers (4)

March 25, 2026 Score: 2 Rep: 234,323 Quality: Medium Completeness: 50%

I haven't experimented with this in C#, but on the other hand, this is one move closer to type classes as in Haskell, and in my experience, I rarely use those for purposes of Dependency Injection (DI). My immediate guess is that this wouldn't be particularly useful in practice.

The best use of DI is to invert dependencies, as in the Dependency Inversion Principle (DIP). The most important result of the DIP is that the client decides what the interface looks like, and the most common implication is that you're going to be left with parameters that don't fit the parameter list of the interface: connection strings for databases, etc. You typically need to get those arguments from configuration or run-time values, so they can't easily be baked in via the type system.

The example of a file hasher looks more like higher-order functions. There might be some use cases where you could use static interfaces, but again, drawing from my experience with Haskell and F#, you often end up with either partially applied functions or lambda expressions when passing functions around as arguments.

Reconsider the file hasher example. What makes it work, as far as I can tell, is that you don't need to initialize an object with any additional data. The question, then, is how often you run into this kind of situation, and at the same time need to vary behaviour. Do you really need polymorphic hashing? Why?

March 25, 2026 Score: 1 Rep: 8,710 Quality: Low Completeness: 40%

Interesting question and I agree with all of your points, but what is the point of SHA256FileHasher : IFileHasher when SHA256FileHasher : IFileHasher works just as well in your example?

March 25, 2026 Score: 0 Rep: 39,346 Quality: Medium Completeness: 60%

I do not think this would be a good idea overall.

The traditional way to handle dependencies and dependency injection would be something like:

public interface IFileHasher
{
    string ComputeHash(string filePath);
}

public class FileProcessor(IFileHasher hasher) { ... }

This allow for lose coupling between components. FileProcessor do not need to care how IFileHasher is implemented. It allow dependencies to be specified at runtime, at the cost of allocating an object, and using virtual calls. For most code this cost is irrelevant. There are also dependency injection containers to help resolve complicated dependency trees.

Abstract interface methods have some use cases for high performance code, like generic math. But I would not consider it a good replacement for managing dependencies in general.

Faster - No allocations. All the speed advantages of static.

Performance is complicated with lots of different factors to consider, so I'm very skeptical about unsupported assertions like this. The standard approach with it comes to performance is to profile the code and optimize the parts that are actually slow.

Clearer semantics

If the effect is that all dependencies are turned into generic type arguments I think this will be much less readable. It is quite common to have fairly complicated dependency trees. If all these dependencies need to be included in the type signature it will become unreadable. Generics is great when used in moderation, but overuse tend to result in unreadable code.

Encourages writing pure functions

I find this amusing since your example seem to interact with the file system, and is therefore anything but pure. I.e. calling ComputeHash with the same file path is not guaranteed to produce the same result every time, since the file might have been changed.

static and pure are orthogonal concepts, one does not imply the other. There is no issue with making instance members pure. This also make some patterns, like memoization, easier to implement.

March 25, 2026 Score: 0 Rep: 19,306 Quality: Low Completeness: 40%

I'm having a hard time understanding the link between FileProcessor and IFileHasher — I understand the static method on SHA256FileHasher, I just don't see how you make the jump from FileProcessor processor = new(new SHA256FileHasher()) to FileProcessor processor = new(). Where does FileProcessor come from?