![]() |
Languages »
C# »
General
Intermediate
License: The Code Project Open License (CPOL)
General Guidelines for C# Class ImplementationBy Eddie VelasquezPractical tips for making your classes good friends with .NET |
C#, Windows, .NET 1.0, Visual Studio, Dev
|
|
Advanced Search Add to IE Search |
|
|
|
||||||||||||||||
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!
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;
}
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;
}
Object.ToString() to return a significant
textual representation.public override string ToString()
{
return string.Format("MyType: {0}", m_data);
}
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.== and
!= in terms of Object.Equals().
public override int GetHashCode()
{
return m_data.GetHashCode();
}
public override bool Equals(object obj)
{
// Call base.Equals() only if this class derives from a
// class that overrides Equals()
if(!base.Equals(obj))
return false;
if(obj == null)
return false;
// Make sure the cast that follows won't fail
if(this.GetType() != obj.GetType())
return false;
// Call this if m_data is a value type
MyType rhs = (MyType) obj;
return m_data.Equals(rhs.m_data);
// Call this if m_data is a reference type
//return Object.Equals(m_data, 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)
{
// Call this if m_data is a value type
return m_data.Equals(rhs.m_data);
// Call this if m_data is a reference type
//return Object.Equals(m_data, 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);
}
[Flags]
attribute.<summary>,
<param> and <returns> elements.[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.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);
}
public object this[int index]
{
get { return m_data[index]; }
set { m_data[index] = value; }
}
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);
}
}
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]; }
}
}
IDisposable...public class MyClass
{
...
public ~MyClass()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this); // Finalization is now unnecessary
}
protected virtual void Dispose(bool disposing)
{
if(!m_disposed)
{
if(disposing)
{
// Dispose managed resources
}
// Dispose unmanaged resources
}
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())
{
...
} // The compiler will call Dispose() on c here
try
{
...
}
catch(Exception ex)
{
Console.WriteLine("Opps! Something failed: {0}", ex.Message);
}
public void DivByZero()
{
int x = 1 / 0; // Our caller better be ready to deal with this!
}
try
{
...
}
catch(Exception ex)
{
// do some cleanup
throw;
}
try
{
...
}
catch(Exception ex)
{
throw new Exception("Something really bad happened!", ex);
}try
{
int i = 1 / 0;
}
catch(DivideByZeroException ex) // Instead of catch(Exception ex)
{
...
}
System.ApplicationException, not from
System.Exception.| 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)
|
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 2 Aug 2002 Editor: Chris Maunder |
Copyright 2002 by Eddie Velasquez Everything else Copyright © CodeProject, 1999-2009 Web10 | Advertise on the Code Project |