On Designing Good Libraries -- Part II
You asked for it, you got it..
feedback always welcome.
Attribute
Usage
•
Suffix with
“Attribute”
•
Perf tip: seal attribute
classes for faster runtime lookup
•
Specify the AttributeUsage attribute
completely
•
Don’t rely on the
defaults!
•
Be as restrictive as
possible
•
You can always open it up
later
[AttributeUsage(
AttributeTargets.All,
Inherited = true,
AllowMultiple = false)]
•
AttributeTargets – where is
the attribute allowed to be applied?
•
Inherited – should
derived members/types be considered to have this
attribute?
•
AllowMultiple – Is it
legal to put more than one instance of the attribute on a particular
member?
•
Use constructor arguments for
required parameters (positional arguments)
•
Provide a read-only property with
the same name
•
Use read-write properties for
optional parameters (named arguments)
•
Never use overloaded
constructors
[AttributeUsage(AttributeTargets.All,
AllowMultiple=true,
Inherited=false)]
public
class NameAttribute : Attribute {
public NameAttribute (string
userName) {..}
public
string UserName {get {..}}
public int Age {get {..} set{..}}
} //end
class
Usage:
[NameAttribute("Bob",
Age=87)]
UserName – positional argument
Age – named
argument
Static
Classes
•
Static classes contain just static
members
•
Compromise between pure OO design
with usability
•
Commonly used
for
•
Shortcuts for other operations
(System.IO.File)
•
Functionality for which a full OO
wrapper is unwarranted (System.Environment)
public
sealed class Environment
{
private Environment(){} //prevents
creation
public static void Exit(int exitCode)
{..}
public static int ExitCode
{
get
{..}
set
{..}
}
public static string CommandLine
{
get
{..}
}
}
•
Best used
when:
•
Clear charter for the
class
•
Not a “miscellaneous”
bucket
•
Not the center point of a
design
•
Use
sparingly
•
Watch out for disconnected
design
•
Static
classes
•
Are
sealed
•
Have private default
constructor
•
No instance
members
•
Static Classes: Bad
Design
•
Late in the final milestone we added
a method to tell if the runtime is being shut down
•
However we added the method as an
instance method making it completely uncallable
•
public sealed class Environment {
private Environment()
{} //Prevent creation
// ---snip---
public bool HasShutdownStarted {
get {
return nativeHasShutdown(); }
}
public
static string UserName { get {...} }
private static extern bool
nativeHasShutdown();
//
---snip---
}
•
Constructors
•
Do minimal work in the
constructor
•
Only capture the
parameters
•
Cost is delayed
•
You can throw exceptions from
constructors
•
Be consistent in the ordering and
naming of constructor parameters
public class
Foo {
private const string defaultA =
"..";
private const
string defaultB = "..";
public Foo():
this(defaultA,defaultB) {}
public Foo(string a):
this(a, defaultB)
{}
public Foo(string a,
string b) {
/* do work here */
}
}
•
Many languages automatically add a
public default constructor if you don’t specify any
•
Abstract classes get a protected
constructor
•
These two code snippets are
equivalent:
public class
Foo {
}
public class
Foo {
public Foo ()
{}
}
•
Always explicitly add a default
constructor to avoid versioning issues
•
Adding a new constructor removes the
default one, breaking clients
//
V1
public class
Foo {
}
Calling Code works:
Foo f = new Foo()
//
V2
public class
Foo {
public Foo (int
value)
}
Calling code
breaks: Foo f = new Foo()
Method
Usage
•
Use overloading only when the
overloads do semantically the same thing
•
Incorrect
overload:
String.IndexOf(string
value) {}
String.IndexOf(char[]
anyOf) {}
•
Correct
overload:
Convert.ToString(int
value) {}
Convert.ToString(double
value) {}
•
Used to avoid
boxing
•
Write(object) works for any
type
•
But specialization avoids
boxing
•
Do only when completely special
casing
public
static void Write (bool value);
public static void Write (int
value);
public static void Write (double value);
public static void Write
(object value);
•
Use appropriate default
values
•
Simple method assumes default
state
•
More complex methods indicate
changes from the default state
MethodInfo
Type.GetMethod (string name);
//ignoreCase =
false
MethodInfo
Type.GetMethod (string name,
boolean
ignoreCase);
•
Use a zeroed state for the default
value (such as: 0, 0.0, false, “”, etc. )
•
Be consistent in the ordering and
naming of method parameters
•
Only the method with the most
parameters should be virtual if needed
public class
Foo {
private const
string defaultForA = "a default";
private const int defaultForB =
42;
public void
Bar(){
Bar(defaultForA, defaultForB);
}
public void Bar (string
a){
Bar(a, defaultForB);
}
public
/*virtual*/ void Bar (string a, int b){
// core
implementation here
}
}
•
Variable number of arguments
(e.g.
printf)
•
Use params
public
static string Format(string format,
params object[]
args);
•
Not used for in/out params (e.g. scanf)
•
Only provide overloads for
performance reasons IF you special case each code
path
void Format
(string formatString, object arg1)
void Format
(string formatString, object arg1, object arg2)
void Format
(string formatString, params object [] args)
•
Allowing method inlining by the
JIT
•
Minimize the use of virtual
methods
•
Don’t write really large
methods
•
Don’t have large numbers of
locals
More to come...
Comments
- Anonymous
July 05, 2003
> Attributes: Never use overloaded constructorsWhat's the reason/idea behind this?Roland - Anonymous
July 05, 2003
- "Late in the final milestone we added a method to tell if the runtime is being shut down. However we added the method as an instance method making it completely uncallable"I might get the wrong idea reading this, but... is this method ever tested? I mean, the developer who hammered in that code, did he/she ever run the method? I don't think so, because how would that be possible? Weird...- Does 'sealing' of classes get you any performance gains when using it on classes with solely static methods? - "Always explicitly add a default constructor to avoid versioning issues". This text is also in the library design guidelines in the .NET reference manual, and I first interpreted incorrectly: I thought I should add a default constructor ALWAYS, even if I already had a constructor in place which accepted parameters. Of course that's not necessary.
- Anonymous
July 06, 2003
In the static classes section, it would be useful to mention the VB.NET Module type. Modules automatically gives you the behavior of a static class, except that it has no constructor, not even a private one, which I'd argue is even better.>Use a zeroed state for the default value (such as: 0, 0.0, false, “”, etc.)Isn't the zeroed state for a string null, not ""? - Anonymous
July 10, 2003
The comment has been removed - Anonymous
March 10, 2004
Does the usage of "Sealed" keyword in a virtual method will allow "JIT inlining"?