Proposal: readonly and sealed interfaces #1790
Replies: 16 comments 2 replies
-
Beta Was this translation helpful? Give feedback.
-
@ufcpp I think attribute would also work. I don't know why this discussion didn't go anywhere tho (discussion issue was closed in 2015). |
Beta Was this translation helpful? Give feedback.
-
@leonidumanskiy Can you link the discussion you're referring to? |
Beta Was this translation helpful? Give feedback.
-
@CyrusNajmabadi It is what @ufcpp linked, plus discussion thread on these notes |
Beta Was this translation helpful? Give feedback.
-
Also, the whole motiviation is outdated now. C# 8 is adding default-method implementation (#52) so, you can safely add new methods, and, if the new methods have a default implementation (even if it's empty) it won't break existing code not using it. Hooray! |
Beta Was this translation helpful? Give feedback.
-
AssemblyA:
public readonly interface IService { }
AssemblyB:
public interface IMyService : IService { }
public class MyImplementation : IMyService { } I agree that "readonly" accessor keyword might not be the best fit, however I could not come up with a better one. If attribute is used instead of an accessor, "InternalImplementationOnly" might be a good option.
|
Beta Was this translation helpful? Give feedback.
-
@leonidumanskiy DIM has been prototyped and discussed amongst the LDM for a long time, and is currently scheduled for C# 8.0. AFAICT it's unlikely that it would be intentionally cut unless they run out of time and require more time to implement/improve. |
Beta Was this translation helpful? Give feedback.
-
Why not make the interfaces internal or private if you don't want users to implement them? The way I see it, an interface shouldn't be part of a public API surface if your users can't implement it. The only instances in which this does happen is COM interop/winRT, but there is a good reason interfaces are used there as API surface as opposed to concrete classes. I really dont see a place where such a feature would be useful. |
Beta Was this translation helpful? Give feedback.
-
I can see some usage cases for |
Beta Was this translation helpful? Give feedback.
-
In the past, when I've had the requirement to prevent additional implementations outside the original assembly, I've been able to achieve the desired result with an abstract class. Prior to C# 7.2, this involved declaring a public abstract class with a private constructor; all the implementations had to be nested within that class in order to access the private constructor. From C# 7.2 it gets easier - the constructor can be declared as private protected, allowing the implementations to be in regular classes instead of being nested. In either case, other implementations are impossible because they can't access the constructor on the abstract class. So, I believe that this is already a solved problem, not one that requires modification of the language. |
Beta Was this translation helpful? Give feedback.
-
@mcosmin222 We use public interfaces in a nuget that is used internally. The interface exists so they can mock it, but the nuget also provides a concrete implementation. e.g.
That way our developers can write testable code that they write against Does this proposal solves this though? |
Beta Was this translation helpful? Give feedback.
-
I see your point, but I don't think we should have language features aimed at unit testing. Having better unit test tools more widely available should be the goal. Interfaces for tests, especially in the case of db objects, is a case of overengineering. You can mock any unsealed class. As for mocking more complex objects, use the right tools for the job. There are plenty around (like Microsoft Fakes), although many are not free. APIs should be designed for human users, not for working around test framework limitations. Don't let the tools dictate your design. The interface should be there to either provide DI support or multiple inheritance. Anyway, I don't think the proposal fixes this. Your mocking object will be considered outside of his dll, so it can't implement the interface. It kind of defeats the whole purpose you are trying to achieve. |
Beta Was this translation helpful? Give feedback.
-
Either way, why not allow consumers to create their own interface that inherits from yours? How can that possibly hurt anything? Since you're still allowing consumers to implement the interface, they'll still break just the same if you add (non-defaulting) members to your interface. |
Beta Was this translation helpful? Give feedback.
-
Defining The Problem Responsibility If a consumer chooses to implement your interface, it is on them to ensure it compiles. If the consumer chooses to upgrade your library, it is on them to make the changes necessary. DIM Summary Perhaps a new versioning scheme for this use-case could be: {libraryVersion}.{majorVersion}.{minorVersion}.{revision}. This would keep the standard 4 number version. Conceptually the libraryVersion represents the whole of the system. Major would be used for any break change (interface additions included); so on and so forth. |
Beta Was this translation helpful? Give feedback.
-
The LDM notes linked by @ufcpp earlier allude to it, but if anyone wants a real-world example, the That attribute is enforced by Personally, I think an attribute+analyzer enforcement is the way to go right now with DIM being the long-term proper solution. No matter what I think there should always be a way for the developer to say "I appreciate the concern, but I don't care." For a project I'm working on right now, it's acceptable for us to be pinned to a specific version of Roslyn with no regard for forward compatibility, so we disable RS1009 in a handful of very narrow cases where the alternative is making the design significantly more complicated. (Without getting too far into it, we mock a couple |
Beta Was this translation helpful? Give feedback.
-
And, right now, sealed class B { }
sealed interface IB { }
class A : B // does not work
class A : IB // works
interface IA : IB // does not work That's inconsistent and confusing and involves redefining what Why not just use a different keyword to do the same exact functionality, but call it |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Quick summary
Introduce new usage for keywords "readonly" and "sealed":
Motivation
Motivation (longer explanation)
When declaring an interface within a library, one could have two possible intentions:
Unfortunately, there is no way to make it clear whether consumers should or should not implement a given interface. As a result, it is not possible to add new methods to interfaces and guarantee that updating the library will not break consumer code.
As a result, when it comes to selecting a versioning strategy for your library you have two options:
While first option guarantees that adding new functionality will not break consumer code, this means that every time new methods are added to existing classes and interfaces, you must increment major version of your nuget package.
Proposed solution solves this by making a mechanism to prevent unintended interface usage. When it's guaranteed that no other code except your assembly implements an interface, you can safely add new methods to that interface and it is guaranteed that doing so will not break consumer code.
Beta Was this translation helpful? Give feedback.
All reactions