|
Introduction
While implementing my first projects using C# I found out that there were several issues
to take into account if I wanted my classes to behave correctly and make good friends with .NET.
This list is more about the "hows" and the "whats" and not the "whys" and while it is in no way
complete, it contains the guidelines that I currently follow. Ah, and I almost forgot...
it's guaranteed to be incomplete!
The Guidelines
- Implement
IComparable.CompareTo() if ordering of objects
makes sense. Also implement operators <, <=,
> and >= in terms of
IComparable.CompareTo().int IComparable.CompareTo(object obj)
{
return m_data - ((MyType)obj).m_data;
}
public static bool operator<(MyType lhs, MyType rhs)
{
return ((IComparable)lhs).CompareTo(rhs) < 0;
}
public static bool operator<=(MyType lhs, MyType rhs)
{
return ((IComparable)lhs).CompareTo(rhs) <= 0;
}
public static bool operator>(MyType lhs, MyType rhs)
{
return ((IComparable)lhs).CompareTo(rhs) > 0;
}
public static bool operator>=(MyType lhs, MyType rhs)
{
return ((IComparable)lhs).CompareTo(rhs) >= 0;
}
- Implement conversion functions only if they actually make sense. Make
conversions explicit if the conversion might fail (throw an exception)
or if the conversion shouldn’t be abused and it’s only necessary for low level
code. Only make conversions implicit if it makes the code clear and
easier to use and the conversion cannot fail.
private MyType(int data)
{
m_data = data;
}
public static explicit operator MyType(int from)
{
return new MyType(from);
}
public static implicit operator int(MyType from)
{
return from.m_data;
}
- Always implement
Object.ToString() to return a significant
textual representation.public override string ToString()
{
return string.Format("MyType: {0}", m_data);
}
- Implement
Object.GetHashCode() and
Object.Equals() if object equality makes sense. If two objects
are equal (Object.Equals() returns true) they should return the
same hash code and this value should be immutable during the whole lifecycle
of the object. The primary key is usually a good hash code for database
objects. For reference types implement operators == and
!= in terms of Object.Equals().
public override int GetHashCode()
{
return m_data.GetHashCode();
}
public override bool Equals(object obj)
{
if(!base.Equals(obj))
return false;
if(obj == null)
return false;
if(this.GetType() != obj.GetType())
return false;
MyType rhs = (MyType) obj;
return m_data.Equals(rhs.m_data);
}
public static bool operator==(MyType lhs, MyType rhs)
{
if(lhs == null)
return false;
return lhs.Equals(rhs);
}
public static bool operator!=(MyType lhs, MyType rhs)
{
return !(lhs == rhs);
}For value types, implement Object.Equals() in terms of a type-safe version
of Equals() to avoid unnecessary boxing and unboxing.
public override int GetHashCode()
{
return m_data.GetHashCode();
}
public override bool Equals(object obj)
{
if(!(obj is MyType))
return false;
return this.Equals((MyType) obj);
}
public bool Equals(MyType rhs)
{
return m_data.Equals(rhs.m_data);
}
public static bool operator==(MyType lhs, MyType rhs)
{
return lhs.Equals(rhs);
}
public static bool operator!=(MyType lhs, MyType rhs)
{
return !lhs.Equals(rhs);
}
- Enumerations that represent bit masks should have the
[Flags]
attribute.
- All classes and public members should be documented using XML comments.
Private members should be documented using normal comments. XML comments
should at least include
<summary>,
<param> and <returns> elements.
- If a class is just meant to be a "container" for static methods (has no
state), it should declare a private parameter-less constructor so it can’t be
instantiated.
- All classes should be CLS compliant. Add an
[assembly:CLSCompliant(true)] attribute in the
AssemblyInfo.cs file. If it is convenient to add a non-CLS compliant
public member add a [CLSCompliant(false)] attribute to it.
- All implementation details should be declared as
private
members. If other classes in the same assembly need access, then declare them
as internal members. Try to expose as little as possible without
sacrificing usability.
strings are immutable objects and always create a new copy
for all the mutating operations, which makes it inefficient for assembling
strings. StringBuilder is a better choice for this task.
object.MemberWiseClone() provides shallow copying. Implement
ICloneable to provide deep copy for classes.
ICloneable.Clone() is usually implemented in terms of a copy
constructor.public MyType(MyType rhs)
{
m_data = rhs.m_data;
}
public object Clone()
{
return new MyType(this);
}
- If a class represents a collection of objects implement one or more
indexers. Indexers are a special kind of property so they can be read-only,
write-only or read-write.
public object this[int index]
{
get { return m_data[index]; }
set { m_data[index] = value; }
}
- For a "collection" class to be used in a
foreach loop it must
implement IEnumerable. IEnumerable.GetEnumerator()
returns a class the implements IEnumerator.public class MyCollection: IEnumerable
{
public IEnumerator GetEnumerator()
{
return new MyCollectionEnumerator(this);
}
}
- The
IEnumerator for a "collection" class is usually
implemented in a private class. An enumerator remains valid as long as the
collection remains unchanged. If changes are made to the collection, such as
adding, modifying or deleting elements, the enumerator is irrecoverably
invalidated and the next call to MoveNext or Reset
throws an InvalidOperationException. If the collection is
modified between MoveNext and Current,
Current will return the element that it is set to, even if the
enumerator is already invalidated.private class MyCollectionEnumerator: IEnumerator
{
public MyCollectionEnumerator(MyCollection col)
{
m_col = col;
m_lastChanged = col.LastChanged;
}
public bool MoveNext()
{
if(m_lastChanged != m_col.LastChanged)
throw new InvalidOperationException();
if(++m_index >= m_col.Data.Count)
return false;
return true;
}
public void Reset()
{
if(m_lastChanged != m_col.LastChanged)
throw new InvalidOperationException();
m_index = -1;
}
public object Current
{
get { return m_col.Data[m_index]; }
}
}
- There is no deterministic destruction in C# (gasp!), which means that the
Garbage Collector will eventually destroy the unused objects. When this
scenario is not ok, implement
IDisposable...public class MyClass
{
...
public ~MyClass()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if(!m_disposed)
{
if(disposing)
{
}
}
m_disposed = true;
}
private bool m_disposed = false;
}
And use the using statement to dispose resources as soon
the object goes out of scopeusing(MyClass c = new MyClass())
{
...
}
- There is no way to avoid exceptions handling in .NET. There are several
strategies when dealing with exceptions:
- Catch the exception and absorb it
try
{
...
}
catch(Exception ex)
{
Console.WriteLine("Opps! Something failed: {0}", ex.Message);
}
- Ignore the exception and let the caller deal with it if there's no
reasonable thing to do.
public void DivByZero()
{
int x = 1 / 0;
}
- Catch the exception, cleanup and re-throw
try
{
...
}
catch(Exception ex)
{
throw;
}
- Catch the exception, add information and re-throw
try
{
...
}
catch(Exception ex)
{
throw new Exception("Something really bad happened!", ex);
}
- When catching exceptions, always try to catch the most specific type that
you can handle.
try
{
int i = 1 / 0;
}
catch(DivideByZeroException ex)
{
...
}
- If you need to define your own exceptions, derive from
System.ApplicationException, not from
System.Exception.
- Microsoft's FxCop
design diagnostic tool is your friend. Use it regularly to validate your
assemblies.
- The definite guide for .NET class library designers is .NET
Framework Design Guidelines.
Updates
| 04/20/2002 |
Item 4 did not mention that GetHashCode's result should be
immutable during the lifecycle of the object. (Thanks to James T.
Johnson) |
|
Item 14 did not mention that if the underlying collection is modified
in any way, the enumerator is invalidated and throws an
InvalidOperationException on calls to MoveNext and
Reset. (Thanks to James T. Johnson) |
|
Item 7 had a typo: instead of "struct" it should be
"class". (Thanks to Blake Coverett) |
| 04/23/2002 |
Added new items about garbage collection and exception handling.
(Thanks to Dale Thompson) |
| 04/25/2002 |
Item 16c has a typo, when rethrowing you should not include the
current exception in the throw statement. (Thanks to Blake
Coverett) |
| 05/14/2002 |
Item 4 suggests implementing == and != in
terms of Objects.Equals() which is ok for reference types but
introduces unnecessary boxing and unboxing for value types. (Thanks to
Eric Gunnerson) |
| 06/03/2002 |
Restructured the code for Equals(), == and != in item 4
to follow a cleaner and more secure implementation. (Thanks to Jeffrey Richter)
|
| You must Sign In to use this message board. |
|
| | Msgs 1 to 25 of 76 (Total in Forum: 76) (Refresh) | FirstPrevNext |
|
|
 |
|
|
I believe that there might be quite many that would be interested in an update (myself included).
I'm not sure how much have changed in C# since the article was last updated, but a number of years have passed since then 
So, Eddie, if you happen to see this... leave a note 
Btw. the article is still great!
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I'm still around and yes... the article does need an update! As soon as things get less hectic here at work I will work on this.
Linux is only free if your time is worthless.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Instead of catching an exception, cleaning something up and then rethrowing it it is often better to use finally without having a catch block at all.
Example:
try { // do something } finally { // cleanup. This is always called, // even when an exception is thrown // in the try block! }
|
| Sign In·View Thread·PermaLink | 4.50/5 (2 votes) |
|
|
|
 |
|
|
I agree You can always catch it somewhere else, but have no chance to clean up elsewhere. It looks and feels silly at first but it makes more than sense when you start working with your code. As an Exception (pun intended), you can place a catch if there is something you really cannot prevent or foresee. An Exception is exactly what it says, an exception state, not just an error state.
top secret Download xacc-ide 0.0.3 now! See some screenshots
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
> ... it is often better to use finally ...
I agree it is often better to use finally.
But just to be clear a finally block is different than catching all exceptions. It is executed even if no exception is thrown.
Andrew Phillips http://www.hexedit.com andrew @ hexedit.com
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Just out of curiosity, why shouldn't private members be documented using XML comments? The Microsoft C# compiler outputs XML documentation for private members in exactly the same way it does for public members. Furthermore, NDoc provides an option for including or excluding private member documentation when generating the final (formatted) documentation.
I personally use XML comments on private members (although I don't normally bother with private fields as their type and name usually describes their purpose sufficiently).
Cheers, Richard.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Well, I guess using "at least" would make the sentence a bit clearer. On the other hand, I believe that XML comments are usually used for generating documentation for public consumption but there's no harm in using them for private members. It's just a matter of taste I guess.
A complex system that does not work is invariably found to have evolved from a simpler system that worked just fine. - Murphy's Law of Computing
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Just a couple of points that I've picked up from various Microsoft articles on exception handling and best practices: -
- Never throw Exception, ApplicationException or SystemException. These exceptions convey no structured information about the error.
- Never catch Exception, ApplicationException or SystemException unless it is the final "catch all" of your program and you are doing so for debugging/logging/error reporting purposes. Catching these exceptions will mask a massive amount of problems, e.g. catching Exception or SystemException would catch InvalidProgramException; an exception that typically signifies a bug in the compiler!
- Only catch exceptions that you EXPECT to be thrown by the code in the try block. Catching more general exceptions may mask bugs.
- Never write an empty catch block unless you're ABSOLUTELY sure you want to turn a blind eye to the exception. Exceptions usually signify a problem that SHOULDN'T be ignored. When failure is likely use a return code or an out argument to signify failure instead (e.g. compare System.Double.TryParse with System.Double.Parse).
- Always aim to validate every argument passed to your method.
- Throw a InvalidEnumArgumentException if an enumeration argument is not a member of the enumeration type.
- Throw an ArgumentOutOfRangeException if a numerical argument is not within the range of valid values.
- Throw an ArgumentNullException if a reference-type argument is null and null is not a valid value.
- Throw an InvalidOperationException if a block of code cannot be executed because of an anomaly with the state of the current object (e.g. calling the Read method of a FileStream object that has been closed).
- Never throw an InvalidOperationException if a block of code cannot be executed because of an anomaly with the state of an external object. InvalidOperationException usually (always?) signifies a bug in the calling code and as such it is usually beneficial to the developer to allow these exceptions to propagate to the top of the program; throw a more meaningful exception instead.
- Avoid propagating low-level exceptions up to the next architectural layer. Instead, wrap them up into more general exceptions so that the implementation details of your abstraction are better hidden from the caller; i.e. don't throw exceptions that have little or no meaning to the caller. E.g. when I call File.Open on a protected file I would expect an UnauthorizedAccessException, not a Microsoft.Ntfs5.PermissionMismatchException.
- Always localise the strings thrown by your exceptions (use ResourceManager). Incidentally, I have heard many conflicting views on this one, but I'm just giving you my opinion. If localisation fails for any reason then I guess your best bet is to throw hard-coded English text (as most people do anyway), but this is debatable.
- Ultimately, never display the Message property of a .NET framework exception to the user. These are not guaranteed to be in the user's native language and usually convey little or no meaning to the user. Instead, only display error messages based on the Message property of your (localised) custom exceptions.
- When throwing custom exceptions, always assume the user may end up reading the message you supply, so no bad language! 
Hope these help. Nice article.
Richard.
|
| Sign In·View Thread·PermaLink | 4.00/5 (1 vote) |
|
|
|
 |
|
|
 |
|
|
Nice. I will eventually post the update to this article and I will certainly add some of your guidelines.
A complex system that does not work is invariably found to have evolved from a simpler system that worked just fine. - Murphy's Law of Computing
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
According to inside sources at Microsoft, implementation of ICloneable should be avoided due to the ambiguity of whether .Clone() method creates a shallow or deep copy. From what I've heard, they are thinking of eventually deprecating ICloneable.
Secondly, deriving from System.ApplicationException is no longer encouraged. A quote from Brad Abrams' MSDN blog,
Designing exception hierarchies is tricky. Well-designed exception hierarchies are wide, not very deep, and contain only those exceptions for which there is a programmatic scenario for catching. We added ApplicationException thinking it would add value by grouping exceptions declared outside of the .NET Framework, but there is no scenario for catching ApplicationException and it only adds unnecessary depth to the hierarchy.
JR - You should not define new exception classes derived from Application-
Exception; use Exception instead. In addition, you should not write code that
catches ApplicationException.
#include "witty_sig.h"
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
(I'm using .Net framework 1.0 on Win2K)
I have implemented a pop3 application in ASP.Net.. which downloads mail from a POP server and updates the DB on click of a button in the page.
Whenever i download large mails (of 5 MB or more) ...the memory consumption of the process 'aspnet_wp.exe' increases dramatically (like 300 MB, which is greater than 60% of the available RAM - 512MB) and is finally recycled by the OS... which results in the download being aborted.
Below i have given the piece of code which actually retrieves the data from the POP server. Please review it suggest me as to why it happens. The memory consumption increases when the processing enters this piece of code.
int BUFFERSIZE = 100000; char [] buffer = new char[BUFFERSIZE]; int len; string strb = ""; try { string prevcur,szTemp,prev; szTemp = RdStrm.ReadLine(); prevcur = prev = szTemp+"\r\n"; was_pop_error(szTemp); if(!error) { while(prevcur.IndexOf("\r\n.\r\n") == -1) { strb += prev; len = 0; len = RdStrm.Read(buffer,0,BUFFERSIZE); string cur = new System.Text.StringBuilder("").Append(buffer,0,len).ToString(); prevcur = prev+cur; prev = cur; } } prevcur = prevcur.Replace("\r\n.\r\n",""); strb += prevcur; return(strb); } catch(InvalidOperationException err) { return("Error in mail download" + err.ToString ()); }
Vikram S
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi Vikram,
If you are doing it synchronously, then it is bound to be very memory hungry. I will like to suggest approach of async calling mechanism. That will be keeping your aspnet_wp.exe free from getting overloaded.
Jayant D. Kulkarni Brainbench Certified Software Engineer in C#, ASP.NET, .NET Framework and ADO.NET
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi,
First of all thank you for your article. It is my first work with sharp and has been great read it.
On the point 12 "one or more indexers", can it be defined more than one? I have and object with several ArrayList and would be great if I can access to any of them as a matrix.
Could you tell me more?
thans in advace.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Cosh wrote something like: On the point 12 "one or more indexers", can it be defined more than one? I have and object with several ArrayList and would be great if I can access to any of them as a matrix.
You can define more than one indexer, as long as they have a different signature. This in contrast to a *real* property, which doesn't have a signature.
The signature of an indexer may have more than one parameter.
We are all men, except for the women.
|
| Sign In·View Thread·PermaLink | 1.00/5 (1 vote) |
|
|
|
 |
|
|
Well written and illustrated. Many thanks for this. Well done!
/********************************** Paul Evans, Dorset, UK. Personal Homepage "EnjoySoftware" @ http://www.enjoysoftware.co.uk/ **********************************/
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Just noticed that 15 says "implement IDisposable" but the class MyClass actually doesn't 
For any newbies, that should be:
public class MyClass : IDisposable { ... }
... also remember that all classes implicitly inherit from object in absence of any explicit base class. Direct Multiple inheritance of classes is sadly not allowed, but you may implement as many interfaces as you like.
Cheers!
/********************************** Paul Evans, Dorset, UK. Personal Homepage "EnjoySoftware" @ http://www.enjoysoftware.co.uk/ **********************************/
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Paul Evans wrote: Just noticed that 15 says "implement IDisposable" but the class MyClass actually doesn't
My bad! Whenever I find time to update this article this will be fixed.
The nice thing about C++ is that only your friends can handle your private parts.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
I have a question to coding conventions. Should I use the private modifier for class members or not?
At time I dont use it. It looks fine for fields but makes methods and properties a little bit hard to see.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I always specify the access modifier. I believe it make the intention clearer.
The nice thing about C++ is that only your friends can handle your private parts.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
First of all, thanks for these helpful guidelines.
Now, on item 5 you mention:
Enumerations that represent bit masks should have the [Flags] attribute.
Why? Please expand on this, or at least provide us with a link that explains it.
Also, what's the "proper" way to check if a bit is set? It certainly isn't as clean-looking as it is in C++. Here's what I mean, see if you concur:
C#
class CSharp { enum Flags { Nope = 1, Yep, Maybe }; int flags; ... void foo() { if (this.flags & (int)Flags.Yep != 0) // <-- have to cast and then turn to boolean expression return; } }
C++
class CPlusPlus { enum Flags { Nope = 1, Yep, Maybe }; int m_flags; ... void foo() { if (m_flags & Yep) // <-- much cleaner return; } }
Thanks, Alvaro
When birds fly in the right formation, they need only exert half the effort. Even in nature, teamwork results in collective laziness. -- despair.com
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Alvaro Mendez wrote: Enumerations that represent bit masks should have the [Flags] attribute. Why? Please expand on this, or at least provide us with a link that explains it. When an enum uses the [flag] attribute, the debugger will display enum values using the identifiers and not the numeric value. Also, you don't have to cast to int to work with the bitwise operators. See the FlagsAttribute[^] class in MSDN.
The nice thing about C++ is that only your friends can handle your private parts.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
| | |