-
Notifications
You must be signed in to change notification settings - Fork 4.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
API Proposal: DelegatingStream #43139
Comments
an alternative design might be a roslyn codefix, which automatically creates the delegated calls to a different Stream, if you create a Type derived from Stream. Resharper has this (in a more generic way) functionality, especially useful for things like Stream which require a lot of boilerplate Code. |
Nice suggestion. It addresses one of the key concerns, that you sort of want to only delegate the functionality that was present at the time you last touched your implementation, and pick up the default base implementations for anything newer that was subsequently added. And it puts the whole implementation in your face such that you can then customize it and choose to not delegate certain members. It does lead to a lot of additional boilerplate code in your project that needs maintaining... but maybe that's the lesser evil. |
Maybe sourcegenerators could be used to automatically generate the boilerplate code and practically "hide" it from the developer. |
Protected basestream rather than public? public Stream BaseStream => _baseStream; |
A public Would the BCL make use of this class? As noted in the issue, it cannot be used as part of a public inheritance hierarchy because it leaks implementation details. Even if just returning If it's not good enough for the BCL then it probably should not be in the BCL. I can see this being useful as a NuGet package owned by the community. |
It's unlikely to be used by any public Stream types. But it'd be used heavily in tests, and it'd likely be used in a few internal stream types. |
I would use this in a few places. Naming consistency... not sure which is more prevalent, but I know of at least |
One problem I can see with this is that we would not be able to delegate any future additions to So, this would be a nice solution but we'd end up with the same problem in the future. i.e. Consider, a user has a custom And now we add that The user would probably want to either throw if there are pending writes, or flush during the But, if we start delegating it, then the |
Yup. This is the second issue discussed in the risks section. |
After touching some ASP.NET (classic) code, I was curious how Sure enough, I stumbled upon these helpers, " |
Maybe we could re-use/extend the source generator proposed in #79839, with a different attribute |
Background and Motivation
Stream-based implementations sometimes want to wrap an existing stream and add a little functionality on top of it, such as doing additional work as part of Dispose:
runtime/src/libraries/System.Net.WebClient/src/System/Net/WebClient.cs
Lines 1982 to 1995 in 1beb1c6
or prohibiting the use of certain operations, e.g.
runtime/src/libraries/System.Net.Http/src/System/Net/Http/StreamContent.cs
Line 144 in 1beb1c6
or tracking additional state as part of certain operations, e.g.
https://github.com/dotnet/wcf/blob/d71557a6ba42bd6d4c5223e978cdafd5b535b970/src/System.Private.ServiceModel/src/System/ServiceModel/Channels/MaxMessageSizeStream.cs#L119-L127
etc.
It can be cumbersome in such cases to have to override every abstract/virtual method on Stream in order to just turn around and delegate all of those calls to the wrapped Stream instance.
Proposed API
Alternative Designs
Risks
Such a type, while useful, is also potentially problematic in a couple of ways:
In some cases it could be tempting to use DelegatingStream as a base type for other public types, e.g. DeflateStream, SslStream, etc. However, doing so essentially exposes implementation detail into the hierarchy of the type; we wouldn't want to do that in any public types in dotnet/runtime, and we'd want to discourage others from doing so as well. Its intended purpose is for internal usage, e.g. creating a custom stream that's exposed publicly as
Stream
rather than as a concrete public type.Various read and write operations have multiple ways they're exposed, e.g. the synchronous Read(byte[], ...) and the asynchronous ReadAsync(byte[], ...). We have a choice, either a) have DelegatingStream only override the abstract methods from the base, such that the other virtuals pick up their default implementations from the base Stream, which will in turn use the abstract methods, or b) delegate each overload to its corresponding member on the _innerStream. Option (a) is problematic, because it means that, for example, ReadAsync will by default just queue a work item to execute Read rather than employing whatever async functionality exists in the wrapped stream's ReadAsync. But option (b) also has some potential pitfalls, e.g. if a developer wants to add some functionality to every read operation, the developer has to override each relevant read overload, and if in the future we add an additional read method, and it delegates directly to the wrapped stream, that new overload will no longer pick up the derived type's intended functionality. This pokes a hole in our strategy of introducing new virtuals that by default use existing methods. This may even be a pit of failure for existing APIs. For example, if someone overrides ReadAsync to customize some functionality but then neglects to override CopyToAsync, then anyone using CopyToAsync will end up skipping the custom logic in ReadAsync, and there's no way to tell C# to delegate to the base's base implementation, which means such a dev would need to re-implement the Stream.CopyToAsync implementation.
The text was updated successfully, but these errors were encountered: