Member Overloading
A member's signature includes its name and parameter list. Each member signature must be unique within the type. Members can have the same name as long as their parameter lists differ. When two or more members in a type are the same kind of member (method, property, constructor, and so on) and have the same name and different parameter lists, the member is said to be overloaded. For example, the Array class contains two CopyTo methods. The first method takes an array and an Int32 value, and the second method takes an array and an Int64 value.
Note |
---|
Changing the return type of a method does not make the method unique as stated in the common language runtime specification. You cannot define overloads that vary only by return type. |
Overloaded members should provide variations on the same functionality. For example, it would not be correct for a type to have two CopyTo members, where the first member copied data to an array and the second copied data to a file. A common use of member overloading is to provide overloads that take few or no parameters and are easy to use. These members call overloads that are more powerful, but also require more experience to use correctly. The easy-to-use overloads support common scenarios by passing default values to the complex overloads. For example, the File class provides overloads for the Open method. The simple overload Open takes a file path and file mode. It calls the Open overload that takes path, file mode, file access, and file share parameters, and provides commonly used default values for the file access and file share parameters. Developers who do not need the flexibility of the complex overload do not have to learn about file access and sharing models before they can open a file.
For ease of maintenance and versioning, the simpler overloads should use the complex overloads to perform their actions; the underlying functionality should not be implemented in multiple places.
Overloading Guidelines
The following guidelines help ensure that your overloaded members are well designed.
Do try to use descriptive parameter names to indicate the default value used by simpler overloads.
This guideline is most applicable for Boolean parameters. The more complex overload's parameter name should indicate the default value supplied by the simpler overload by describing the opposite state or action. For example, the String class provides the following overloads:
Overloads Public Shared Function Compare( _
ByVal strA As String, _
ByVal strB As String _
) As Integer
Overloads Public Shared Function Compare( _
ByVal strA As String, _
ByVal strB As String, _
ByVal ignoreCase As Boolean _
) As Integer
public static int Compare(
string strA,
string strB
);
public static int Compare(
string strA,
string strB,
bool ignoreCase
);
The second overload provides a Boolean parameter named ignoreCase. This indicates that the simpler overload is case-sensitive and you need to use the more complex overload only when you want to ignore casing. In general, the default value should usually be false.
Avoid arbitrarily varying parameter names in overloads. If a parameter in one overload represents the same input as a parameter in another overload, the parameters should have the same name.
For example, do not do the following:
Public Sub Write(message as String, stream as FileStream)
End Sub
Public Sub Write(line as String, file as FileStream, closeStream as Boolean)
End Sub
public void Write(string message, FileStream stream){}
public void Write(string line, FileStream file, bool closeStream){}
public:
void Write(String^ message, FileStream^ stream){}
void Write(String^ line, FileStream^ file, bool closeStream){}
The correct definition for these overloads is as follows:
Public Sub Write(message as String, stream as FileStream)
End Sub
Public Sub Write(message as String, stream as FileStream, _
closeStream as Boolean)
End Sub
public void Write(string message, FileStream stream){}
public void Write(string message, FileStream stream, bool closeStream){}
public:
void Write(String^ message, FileStream^ stream){}
void Write(String^ message, FileStream^ stream, bool closeStream){}
Do be consistent in the ordering of parameters in overloaded members. Parameters with the same name should appear in the same position in all overloads.
For example, do not do the following:
Public Sub Write( message as String, stream as FileStream)
End Sub
Public Sub Write(stream as FileStream, message as String, _
closeStream as Boolean)
End Sub
public void Write(string message, FileStream stream){}
public void Write(FileStream stream, string message, bool closeStream){}
public:
void Write(String^ message, FileStream^ stream){}
void Write(FileStream^ stream, String^ message, bool closeStream){}
The correct definition for these overloads is as follows:
Public Sub Write(message as String, stream as FileStream)
End Sub
Public Sub Write(message as String, stream as FileStream, _
closeStream as Boolean)
End Sub
public void Write(string message, FileStream stream){}
public void Write(string message, FileStream stream, bool closeStream){}
public:
void Write(String^ message, FileStream^ stream){}
void Write(String^ message, FileStream^ stream, bool closeStream){}
This guideline has two constraints:
If an overload takes a variable argument list, the list must be the last parameter.
If the overload takes out parameters, by convention these should appear as the last parameters.
Do make only the longest overload virtual (Overridable in Visual Basic) if extensibility is required. Shorter overloads should simply call through to a longer overload.
The following code example demonstrates this practice.
Public Sub Write(message as String, stream as FileStream)
Me.Write(message, stream, false)
End Sub
Public Overridable Sub Write( _
message as String, stream as FileStream, closeStream as Boolean)
' Do work here.
End Sub
public void Write(string message, FileStream stream)
{
this.Write(message, stream, false);
}
public virtual void Write(string message, FileStream stream, bool closeStream)
{
// Do work here.
}
public:
void Write(String^ message, FileStream^ stream)
{
this->Write(message, stream, false);
}
virtual void Write(String^ message, FileStream^ stream, bool closeStream)
{
// Do work here.
}
Do not use ref or out modifiers to overload members.
For example, do not do the following.
Public Sub Write(message as String, count as Integer)
...
Public Sub Write(message as String, ByRef count as Integer)
public void Write(string message, int count)
...
public void Write(string message, out int count)
public:
void Write(String^ message, int count)
...
void Write(String^ message, int% count)
In general, if you have a design where this occurs, there is most likely a deeper design issue. Consider whether one of the members should be renamed to provide more information about the exact action being taken by the method.
Do allow null (Nothing in Visual Basic) to be passed for optional arguments. If a method takes optional arguments that are reference types, allow null to be passed to indicate that the default value should be used. This avoids the problem of having to check for null before calling a member.
For example, developers should not have to check for null in the following example.
Public Sub CopyFile (source as FileInfo, _
destination as DirectoryInfo, _
newName as string)
If newName Is Nothing
InternalCopyFile(source, destination)
Else
InternalCopyFile(source, destination, newName)
End If
End Sub
public void CopyFile (FileInfo source, DirectoryInfo destination, string newName)
{
if (newName == null)
{
InternalCopyFile(source, destination);
}
else
{
InternalCopyFile(source, destination, newName);
}
}
public:
void CopyFile(FileInfo^ source, DirectoryInfo^ destination, String^ newName)
{
if (newName == nullptr)
{
InternalCopyFile(source, destination);
}
else
{
InternalCopyFile(source, destination, newName);
}
}
Do use member overloading rather than defining members with default arguments. Default arguments are not CLS-compliant and cannot be used from some languages.
The following code example demonstrates the incorrect method design.
Public Sub Rotate (data as Matrix, Optional degrees as Integer = 180)
' Do rotation here
End Sub
This code should be redesigned as two overloads with the simpler overload providing the default value. The following code example demonstrates the correct design.
Overloads Public Sub Rotate (data as Matrix)
Rotate(data, 180)
End Sub
Overloads Public Sub Rotate (data as Matrix, degrees as Integer)
' Do rotation here
End Sub
Portions Copyright 2005 Microsoft Corporation. All rights reserved.
Portions Copyright Addison-Wesley Corporation. All rights reserved.
For more information on design guidelines, see the "Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Libraries" book by Krzysztof Cwalina and Brad Abrams, published by Addison-Wesley, 2005.