Skip to content
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

Move IDisposable implementation declaration from inheritees to parent #746

Open
wants to merge 3 commits into
base: develop
Choose a base branch
from

Conversation

bad-samaritan
Copy link

Hello,
I was using SSH.NET library in my project and I noticed that by following inheritance rules I fell into a trap.

My SFTP wrapper class contains private field of a general AuthenticationMethod type.
It is is assigned in different constructors depending on selected authentication method.

	public class SftpFileHandler : IDisposable
	{
(...)
		private readonly AuthenticationMethod _authMethod;
(...)

		public SftpFileHandler(string handlerName, string address, int port, string? relativeWorkingDir, string? username, string? password, bool? keepConnectionOpen)
		{
(...)
			_authMethod = new PasswordAuthenticationMethod(username, password ?? "");
(...)
		}

		public SftpFileHandler(string handlerName, string address, int port, string? relativeWorkingDir, string? username, string privateKeyFilePath, string? privateKeyPass, bool? keepConnectionOpen)
		{
(...)
			_authMethod = new PrivateKeyAuthenticationMethod(Username, new PrivateKeyFile(privateKeyFilePath, privateKeyPass ?? ""));
(...)
		}

When I was implementing Dispose() for SftpFileHandler I noticed that AuthenticationMethod does not require disposing which seemed a bit suspicious to me since it may contain sensitive data that may require proper disposing.
So I browsed descendants of AuthenticationMethod to discover that all 4 of them implement IDisposable interface.
That being said in other to properly dispose the whole SftpFileHandler I need to do something like:

	public class SftpFileHandler : IDisposable
	{
(...)
		protected virtual void Dispose(bool disposing)
		{
			if (!_disposed)
			{
				if (_authMethod != null && _authMethod is IDisposable disposableAuthMethod)
				{
					disposableAuthMethod.Dispose();
				}
(...)

Which is always true...
Instead of just simply:

	public class SftpFileHandler : IDisposable
	{
(...)
		protected virtual void Dispose(bool disposing)
		{
			if (!_disposed)
			{
				if (_authMethod != null)
				{
					_authMethod .Dispose();
				}
(...)

I would like to propose a small improvement that would make disposing requirement move visible thus making it easier to maintain a little bit more secure code.
My suggestion is to move IDisposable a level above -> to the AuthenticationMethod:

namespace Renci.SshNet
{
    /// <summary>
    /// Base class for all supported authentication methods
    /// </summary>
    public abstract class AuthenticationMethod : IAuthenticationMethod, IDisposable
    {

This way code analyzing tools would warn programmers before making a mistake of not disposing inheritee of AuthenticationMethod class.

I tested changes mentioned above in my code and they work as expected.

What do you think about this improvement?

@IgorMilavec
Copy link
Collaborator

Dispose pattern should be implemented in accordance with Implement a Dispose method to avoid code duplication.

@bad-samaritan
Copy link
Author

bad-samaritan commented May 16, 2021

Hey @IgorMilavec, thanks for the feedback.
I moved common Dispose stuff along with some fields to AuthenticationMethod to avoid code duplication - see 8ca32fa.
Looks better?

Copy link
Collaborator

@IgorMilavec IgorMilavec left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks much cleaner now. Why not instantiate _authenticationCompleted in AuthenticationMethod's constructor? Also, do not declare finalizers, if they do not actually perform cleanup.

@bad-samaritan
Copy link
Author

bad-samaritan commented May 18, 2021

Why not instantiate _authenticationCompleted in AuthenticationMethod's constructor?

Because derived classes use different types for the EventWaitHandle : PrivateKeyAuthenticationMethod is using ManualResetEvent and the rest three are using AutoResetEvent.
We can also instantiate it with AutoResetEvent in AuthenticationMethod ctor and override in PrivateKeyAuthenticationMethod to be ManualResetEvent. I assumed the first one is clearer because it indicates that this field is used differently in derived classes but I don't have a strong opinion on that.

Also, do not declare finalizers, if they do not actually perform cleanup.

Totally agree! Especially that classes with finalizers can take more time to dispose, so removing them is actaully small performance optimization.
However, this was another part of the code that I did not touch because it was not primary objective of this pull request. But sure, let's also correct that.

Updated code without finalizers on c34ad23.

@IgorMilavec
Copy link
Collaborator

Huh, I totally missed different EventWaitHandles... I understand now. :)

Copy link
Collaborator

@IgorMilavec IgorMilavec left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good to me.

@bad-samaritan
Copy link
Author

Pleasure doing business with you.

Thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants