This class is the first of an open source project I just (a couple of days ago) started. The projects is meant to simulate circuits (logic gates and stuff) and this class is meant to manage pins. I'm developing everything following the Observer design pattern.
Could you take a look at the code and tell me anything you see that is wrong or could be better? I mean anything: readability issues, naming, comments, indentation, you name it.
/// <summary>
/// A pin used in the various logical gates.
/// </summary>
public class Pin : Observable<Pin>, IComparable<Pin>, IComparable<int>
{
#region variables
private PinValue _value;
private string _code;
private string _label;
#endregion
#region properties
/// <summary>
/// Get the ID of the Pin.
/// </summary>
public Guid Id
{
get;
private set;
}
/// <summary>
/// Get or Set the value of the Pin.
/// </summary>
public PinValue Value
{
get
{
return _value;
}
set
{
_value = value;
Notify(this);
}
}
/// <summary>
/// Get or Set the code of the Pin.
/// </summary>
public string Code
{
get
{
return _code;
}
set
{
_code = value;
Notify(this);
}
}
/// <summary>
/// Get or Set the label of the Pin.
/// </summary>
public string Label
{
get
{
return _label;
}
set
{
_label = value;
Notify(this);
}
}
#endregion
#region constructors
/// <summary>
/// The default constructor of a Pin.
/// </summary>
public Pin()
{
Id = Guid.NewGuid();
Value = PinValue.Unknown;
Code = Id.ToString();
Label = null;
}
/// <summary>
/// A constructor of a Pin.
/// </summary>
/// <param name="value">
/// The initial value of the new Pin.
/// </param>
public Pin(PinValue value)
: this()
{
Value = value;
}
/// <summary>
/// A constructor of a Pin.
/// </summary>
/// <param name="code">
/// The initial code of the new Pin.
/// </param>
public Pin(string code)
: this()
{
Code = code;
}
/// <summary>
/// A constructor of a Pin.
/// </summary>
/// <param name="value">
/// The initial value of the new Pin.
/// </param>
/// <param name="code">
/// The initial code of the new Pin.
/// </param>
public Pin(PinValue value, string code)
: this(value)
{
Code = code;
}
/// <summary>
/// A constructor of a Pin.
/// </summary>
/// <param name="code">
/// The initial code of the new Pin.
/// </param>
/// <param name="label">
/// The initial label of the new Pin.
/// </param>
public Pin(string code, string label)
: this(code)
{
Label = label;
}
/// <summary>
/// A constructor of a Pin.
/// </summary>
/// <param name="value">
/// The initial value of the new Pin.
/// </param>
/// <param name="code">
/// The initial code of the new Pin.
/// </param>
/// <param name="label">
/// The initial label of the new Pin.
/// </param>
public Pin(PinValue value, string code, string label)
: this(value, code)
{
Label = label;
}
#endregion
#region methods
/// <summary>
/// A method that builds and returns a string
/// representation of a Pin.
/// </summary>
/// <returns>
/// A string representation of a Pin.
/// </returns>
public override string ToString()
{
return string.Format(
"[Id: {0} | Code: {1} | Label: {2} | Value: {3}]",
Id,
Code,
Label,
Value);
}
/// <summary>
/// A method that builds and returns the
/// hash code of a Pin.
/// </summary>
/// <returns>
/// The hash code of a Pin.
/// </returns>
public override int GetHashCode()
{
// Id is the only immutable value of this object
return Id.GetHashCode();
}
/// <summary>
/// A method that checks if this Pin is
/// equal to another object.
/// </summary>
/// <param name="obj">
/// The object to compare for equality with
/// this Pin.
/// </param>
/// <returns>
/// TRUE if this Pin and the specified object
/// are equal, FALSE otherwise.
/// </returns>
/// <remarks>
/// With the term "equality" is intended the
/// equality of the combination [code|label|value]
/// of the Pin. For an equality check of only the
/// value use the "==" operator.
/// </remarks>
public override bool Equals(object obj)
{
return obj != null
&& (obj is Pin)
&& Equals(obj as Pin);
}
/// <summary>
/// A method that checks if this Pin is
/// equal to another Pin.
/// </summary>
/// <param name="otherPin">
/// The Pin to compare for equality with
/// this Pin.
/// </param>
/// <returns>
/// TRUE if this Pin and the specified other
/// Pin are equal, FALSE otherwise.
/// </returns>
public bool Equals(Pin otherPin)
{
if (otherPin == null)
{
return false;
}
return Code == otherPin.Code
&& Value == otherPin.Value
&& Label == otherPin.Label;
}
/// <summary>
/// Compare this Pin to another Pin.
/// </summary>
/// <param name="other">
/// The other Pin to compare against
/// this Pin.
/// </param>
/// <returns>
/// -1 if this Pin is smaller than the other.
/// 0 if this Pin is equal (in terms of its value) to the other.
/// 1 if this Pin is greater than the other.
/// </returns>
public int CompareTo(Pin other)
{
if (other == null)
{
throw new InvalidOperationException(
"The other Pin must not be null for the operation to be possible");
}
// suppose they are equal by default.
int result = 0;
if (Value < other.Value)
{
result = -1;
}
else if (Value > other.Value)
{
result = 1;
}
return result;
}
/// <summary>
/// Compare this Pin's value to an Integer.
/// </summary>
/// <param name="other">
/// The integer value to compare against this
/// Pin's value.
/// </param>
/// <returns>
/// -1 if this Pin's value is smaller than the integer value.
/// 0 if this Pin's value is equal to the integer value.
/// 1 if this Pin's value is greater than the integer value.
/// </returns>
/// <remarks>
/// The considered integer values are -1, 0, 1. In case a value
/// out of the indicated range is given then it's is reduced to
/// one of the three values indicated.
/// </remarks>
public int CompareTo(int other)
{
// possible values are -1, 0, 1.
int toCompare = other >= 1
? 1
: other <= -1
? -1
: 0;
// possible values are -1, 0, 1.
int value = (int)Value - 1;
// possible values are -2, -1, 0, 1, 2.
int difference = value - toCompare;
return difference < 0
? -1
: difference > 0
? 1
: 0;
}
#endregion
#region operators
/// <summary>
/// Checks for equality among the values of the two Pins.
/// </summary>
/// <param name="left">
/// The left Pin of the operator.
/// </param>
/// <param name="right">
/// The right Pin of the operator.
/// </param>
/// <returns>
/// TRUE if the two Pins have the same value, FALSE otherwise.
/// </returns>
public static bool operator ==(Pin left, Pin right)
{
object leftObj = (object)left;
object rightObj = (object)right;
bool result = leftObj == null && rightObj == null;
if (leftObj != null
&& rightObj != null
&& !result) // if result is true then
// there is no need to enter here
{
result |= left.Value == right.Value;
}
return result;
}
/// <summary>
/// Checks for inequality among the values of the two Pins.
/// </summary>
/// <param name="left">
/// The left Pin of the operator.
/// </param>
/// <param name="right">
/// The right Pin of the operator.
/// </param>
/// <returns>
/// TRUE if the two Pins have different values, FALSE otherwise.
/// </returns>
public static bool operator !=(Pin left, Pin right)
{
// objects created to check if Pins are null
// If Pins were used then it would make recursive calls
object leftObj = (object)left;
object rightObj = (object)right;
// did it this way to use the lazy evaluation of
// the || and && operators
return (leftObj == null && rightObj != null)
|| (leftObj != null && rightObj == null)
|| (leftObj != null
&& rightObj != null
&& left.Value != right.Value);
}
/// <summary>
/// Checks if the left Pin's value is smaller than the
/// right one's.
/// </summary>
/// <param name="left">
/// The left Pin of the operator.
/// </param>
/// <param name="right">
/// The right Pin of the operator.
/// </param>
/// <returns>
/// TRUE if the left Pin's value is smaller than the right one's,
/// FALSE otherwise.
/// </returns>
public static bool operator <(Pin left, Pin right)
{
return left != null
&& right != null
&& left.Value < right.Value;
}
/// <summary>
/// Checks if the left Pin's value is smaller than, or
/// equal to, the right one's.
/// </summary>
/// <param name="left">
/// The left Pin of the operator.
/// </param>
/// <param name="right">
/// The right Pin of the operator.
/// </param>
/// <returns>
/// TRUE if the left Pin's value is smaller than, or equal to,
/// the right one's, FALSE otherwise.
/// </returns>
public static bool operator <=(Pin left, Pin right)
{
return left != null
&& right != null
&& left.Value <= right.Value;
}
/// <summary>
/// Checks if the left Pin's value is greater than the
/// right one's.
/// </summary>
/// <param name="left">
/// The left Pin of the operator.
/// </param>
/// <param name="right">
/// The right Pin of the operator.
/// </param>
/// <returns>
/// TRUE if the left Pin's value is greater than the right one's,
/// FALSE otherwise.
/// </returns>
public static bool operator >(Pin left, Pin right)
{
return left != null
&& right != null
&& left.Value > right.Value;
}
/// <summary>
/// Checks if the left Pin's value is greater than, or
/// equal to, the right one's.
/// </summary>
/// <param name="left">
/// The left Pin of the operator.
/// </param>
/// <param name="right">
/// The right Pin of the operator.
/// </param>
/// <returns>
/// TRUE if the left Pin's value is greater than, or equal
/// to, the right one's, FALSE otherwise.
/// </returns>
public static bool operator >=(Pin left, Pin right)
{
return left != null
&& right != null
&& left.Value >= right.Value;
}
/// <summary>
/// The logical AND operator.
/// </summary>
/// <param name="left">
/// The left Pin of the operator.
/// </param>
/// <param name="right">
/// The right Pin of the operator.
/// </param>
/// <returns>
/// TRUE if the left pin's value and the right pin's value are High.
/// FALSE otherwise.
/// </returns>
public static bool operator &(Pin left, Pin right)
{
return left != null
&& right != null
&& left.Value == PinValue.High
&& right.Value == PinValue.High;
}
/// <summary>
/// The logical OR operator.
/// </summary>
/// <param name="left">
/// The left Pin of the operator.
/// </param>
/// <param name="right">
/// The right Pin of the operator.
/// </param>
/// <returns>
/// TRUE if the left pin's value or the right pin's value is High.
/// FALSE otherwise.
/// </returns>
public static bool operator |(Pin left, Pin right)
{
return (left != null && left.Value == PinValue.High)
|| (right != null && right.Value == PinValue.High);
}
/// <summary>
/// The logical XOR operator.
/// </summary>
/// <param name="left">
/// The left Pin of the operator.
/// </param>
/// <param name="right">
/// The right Pin of the operator.
/// </param>
/// <returns>
/// TRUE if the pins have values that are different from each other and also different from Undefined.
/// FALSE otherwise.
/// </returns>
public static bool operator ^(Pin left, Pin right)
{
return left != null
&& right != null
&& left.Value != PinValue.Unknown
&& right.Value != PinValue.Unknown
&& left.Value != right.Value;
}
/// <summary>
/// The logical NOT operator.
/// </summary>
/// <param name="pin">
/// The only Pin of the operator.
/// </param>
/// <returns>
/// TRUE if the pin's value is Low.
/// FALSE if the pin's value is not Low.
/// </returns>
public static bool operator !(Pin pin)
{
return pin != null
&& pin.Value == PinValue.Low;
}
/// <summary>
/// The absolute TRUE operator.
/// </summary>
/// <param name="pin">
/// The only Pin of the operator.
/// </param>
/// <returns>
/// TRUE if the Pin's value is High.
/// FALSE otherwise.
/// </returns>
public static bool operator true(Pin pin)
{
bool result = false;
if (pin != null && pin.Value == PinValue.High)
{
result = true;
}
return result;
}
/// <summary>
/// The absolute FALSE operator.
/// </summary>
/// <param name="pin">
/// The only Pin of the operator.
/// </param>
/// <returns>
/// TRUE if the Pin's value is Low.
/// FALSE otherwise.
/// </returns>
public static bool operator false(Pin pin)
{
bool result = false;
if (pin != null && pin.Value == PinValue.Low)
{
result = true;
}
return result;
}
/// <summary>
/// Explicit conversion operator from Integer to Pin.
/// </summary>
/// <param name="val">
/// The Integer value to convert to a Pin.
/// </param>
/// <returns>
/// The corresponding Pin.
/// </returns>
/// <remarks>
/// val == 0 corresponds to Low.
/// val &st; 0 corresponds to Undefined.
/// val > 0 corresponds to High.
/// </remarks>
public static explicit operator Pin(int val)
{
Pin result = new Pin();
if (val == 0)
{
result.Value = PinValue.Low;
}
else if (val > 0)
{
result.Value = PinValue.High;
}
return result;
}
/// <summary>
/// Explicit conversion operator from Pin to Integer.
/// </summary>
/// <param name="val">
/// The Pin to convert to Integer.
/// </param>
/// <returns>
/// The corresponding Integer.
/// </returns>
/// <remarks>
/// val.Undefined corresponds to -1.
/// val.Low corresponds to 0.
/// val.High corresponds to 1.
/// </remarks>
public static explicit operator int(Pin val)
{
return (int)val.Value;
}
/// <summary>
/// Implicit conversion operator from bool? to Pin.
/// </summary>
/// <param name="val">
/// The bool? to convert to Pin.
/// </param>
/// <returns>
/// The corresponding Pin.
/// </returns>
/// <remarks>
/// NULL corresponds to Undefined.
/// TRUE corresponds to High.
/// FALSE corresponds to Low.
/// </remarks>
public static implicit operator Pin(Nullable<bool> val)
{
Pin result = new Pin();
if (val != null)
{
if (val == true)
{
result.Value = PinValue.High;
}
else
{
result.Value = PinValue.Low;
}
}
return result;
}
/// <summary>
/// Explicit conversion operator from Pin to bool?.
/// </summary>
/// <param name="val">
/// The Pin value to convert to bool?.
/// </param>
/// <returns>
/// The corresponding bool?.
/// </returns>
/// <remarks>
/// val.Undefined corresponds to NULL.
/// val.Low corresponds to FALSE.
/// val.High corresponds to TRUE.
/// </remarks>
public static explicit operator Nullable<bool>(Pin val)
{
bool? result = null;
if (val.Value == PinValue.Low)
{
result = false;
}
else if (val.Value == PinValue.High)
{
result = true;
}
return result;
}
/// <summary>
/// Implicit conversion operator from bool to Pin.
/// </summary>
/// <param name="val">
/// The bool to convert to Pin.
/// </param>
/// <returns>
/// The corresponding Pin.
/// </returns>
/// <remarks>
/// TRUE corresponds to High.
/// FALSE corresponds to Low and Undefined.
/// </remarks>
public static implicit operator Pin(bool val)
{
return new Pin(val ? PinValue.High : PinValue.Low);
}
/// <summary>
/// Explicit conversion operator from Pin to bool?.
/// </summary>
/// <param name="val">
/// The Pin value to convert to bool?.
/// </param>
/// <returns>
/// The corresponding bool?.
/// </returns>
/// <remarks>
/// val.Undefined and val.Low correspond to FALSE.
/// val.High corresponds to TRUE.
/// </remarks>
public static explicit operator bool(Pin pin)
{
return pin != null && pin.Value == PinValue.High;
}
#endregion
}
3 Answers 3
Some bigger picture thoughts:
Honestly it sounds to me like you are conflating two distinct concepts here. One is a "digital value" concept that supports all the various operations, and comparisons. That should probably be an immutable value type (struct), and would have helpful implicit conversions. Then you have a separate pin class that is observable, but not comparable or convertible, and which uses the digital value class as its value property. This separate pin class would have the GUID and label, etc.
Consider how the class is right now. You have comparison operators defined on the pins. The problem is that logically you are not comparing the pins. You are logically comparing the values on the pins. So it is better to have the pins store a custom value type with the comparison operators implemented on them. Similarly with the conversions to or from other data types. This is especially true with the conversions to a Pin
, because Pins are parts of Gates (or other components) and we generally don't want to just create them out of thin ain, except as part of creating a gate itself.
Also as it is right now, The Equals method provides no value. Because the GUID is immutable and unique per instance, Equals is Equivalent to ReferenceEquals, which is the default implementation of Equals. Just drop Equals and GetHashCode.
So I would suggest that the Pin class would look more like:
public class Pin : Observable<Pin>
{
private LogicValue _value;
private string _code;
private string _label;
public Guid Id
{
get;
private set;
}
public PinValue Value
{
get
{
return _value;
}
set
{
_value = value;
Notify(this);
}
}
public string Code
{
get
{
return _code;
}
set
{
_code = value;
Notify(this);
}
}
public string Label
{
get
{
return _label;
}
set
{
_label = value;
Notify(this);
}
}
#endregion
#region constructors
public Pin()
{
Id = Guid.NewGuid();
Value = PinValue.Unknown;
Code = Id.ToString();
Label = null;
}
public Pin(string code)
: this()
{
Code = code;
}
public Pin(LogicValue value, string code)
: this(value)
{
Code = code;
}
public Pin(string code, string label)
: this(code)
{
Label = label;
}
public Pin(LogicValue value, string code, string label)
: this(value, code)
{
Label = label;
}
}
As for the LogicValue class I've posted an implementation as a Gist (because it is a bit long, there are licensing considerations for posting it here).
So I've basically replaced the PinValue
enum with a more full featured class that encapsulates the calculations on logic Values. Then the pin becomes more an an observable set of attributes, at least for the moment. At some point I presume you will be adding a way to wire pins together, and perhaps at that point, you would add methods so that an input pin can observe the wire it is connected to, and update it's value when the wire's value changes. Or perhaps not.
The underlying idea is to separate out the logic about calculating values from a class that merely represents the pin of a component/gate.
-
\$\begingroup\$ If I understood correctly your point, the Pin class contains too much info (some of which do not logically belong to it) and from it should derive another structure, one that contains the actual value of the pin and its various conversions and operations. Is that right? \$\endgroup\$Gentian Kasa– Gentian Kasa2015年09月30日 08:06:39 +00:00Commented Sep 30, 2015 at 8:06
-
1\$\begingroup\$ @GentianKasa: I've rephrased my answer, but the main idea is seperation of concerns. This class represents a pin, and should not be conserded with converting values to or from a representation of logic value states (high/low/unknown). That is a seperate concern that should have its own class. \$\endgroup\$Kevin Cathcart– Kevin Cathcart2015年09月30日 18:40:02 +00:00Commented Sep 30, 2015 at 18:40
-
\$\begingroup\$ I got it. Really helpful indeed. Thanks a lot for the great feedback. \$\endgroup\$Gentian Kasa– Gentian Kasa2015年10月01日 05:31:47 +00:00Commented Oct 1, 2015 at 5:31
nulls
I cannot imagine a scenario in which I'd write
x < null
and expect a meaningful result. Exception is the most logical outcome.In fact,
operator <
is expected to impose a strict weak ordering. Particularly if botha < b
andb < a
arefalse
, it logically follows thata == b
. In your implementation bothx < null
andnull < x
are false. Shall I infer thatx == null
?Comparators
I highly recommend to implement all the comparators in terms of
operator<
, for example:operator >(Pin x, Pin y) { return y < x; } operator ==(Pin x, Pin y) { return !(x < y) && !(y < x); }
etc. Such approach immensely simplifies reasoning about the code.
If you do not want to have
operator ==
this way, you still must haveoperator !=
asreturn ! (x == y);
-
\$\begingroup\$ I didn't quite think about this scenario. Thanks for the excellent feedback. I changed the implementation so it throws an InvalidOperationException in case a null value is passed to <,>,<=,>= and I kept the current implementation of the == operator in order to be able to compare it to null using that operator. All the comparators are based on the '<' and '==' operator. \$\endgroup\$Gentian Kasa– Gentian Kasa2015年09月29日 18:59:50 +00:00Commented Sep 29, 2015 at 18:59
Toooo mmuuuuccccchhhhh coooommmmeeeeennnnttttssss!!!!!!
Consider the following :
/// <summary>
/// A method that builds and returns the
/// hash code of a Pin.
/// </summary>
/// <returns>
/// The hash code of a Pin.
/// </returns>
public override int GetHashCode()
{
// Id is the only immutable value of this object
return Id.GetHashCode();
}
I know it's a method, everyone will know it's a method. And the return is pretty obvious too. Keep it simple, where you don't think comments are a must, don't use them. It's your tool, use it wisely :)
/// <summary>
/// Returns the hashcode of a Pin.
/// </summary>
public override int GetHashCode()
{
// Id is the only immutable value of this object
return Id.GetHashCode();
}
That's clear, ain't it?
Second example, just to make sure it's all clear :
/// <summary>
/// A method that checks if this Pin is
/// equal to another object.
/// </summary>
/// <param name="obj">
/// The object to compare for equality with
/// this Pin.
/// </param>
/// <returns>
/// TRUE if this Pin and the specified object
/// are equal, FALSE otherwise.
/// </returns>
/// <remarks>
/// With the term "equality" is intended the
/// equality of the combination [code|label|value]
/// of the Pin. For an equality check of only the
/// value use the "==" operator.
/// </remarks>
public override bool Equals(object obj)
{
return obj != null
&& (obj is Pin)
&& Equals(obj as Pin);
}
The remark is useless. Don't forget that your code is meant to be read by people who know how to program. Programmers know that ==
is for reference (for classes at least) and Equals
is meant to be custom. Therefore we know that if the return type is bool
for a method named Equals
, it will be true
if objects are equals and false
otherwise. Also, note that I brought back on a single line the param
. You should do this most of the time. The header comments will mostly be read using Visual Studio's intellisense. Which means it'll be easy to read even if it looks unreadable in your code.
/// <summary>
/// Checks if this Pin is equal to another object.
/// </summary>
/// <param name="obj"> The object to compare for equality with this Pin.</param>
public override bool Equals(object obj)
{
return obj != null
&& (obj is Pin)
&& Equals(obj as Pin);
}
Header's comments should be helpful short information. We (you included), as developpers, will try your code and understand what it does.
On a side note, I don't like regions
. Maybe that's just me though. If your class is small enough (and it should be if you remove some comments), regions just bloat the code!
What I'm trying to say is : The code has all the value (ok, maybe 95%), make it look this way in the way you write it :)
-
\$\begingroup\$ Yeah, maybe i've overdone it with the comments :) Regarding 'regions', i just use them to expand and collapse the areas of the code i'm working on, but i guess that's just me. Thanks a lot for the tips \$\endgroup\$Gentian Kasa– Gentian Kasa2015年09月29日 17:52:29 +00:00Commented Sep 29, 2015 at 17:52
-
\$\begingroup\$ You're welcomed! As I said, the regions is just a matter of taste I guess \$\endgroup\$IEatBagels– IEatBagels2015年09月29日 18:02:51 +00:00Commented Sep 29, 2015 at 18:02
-
1\$\begingroup\$ These XML comments are actually pretty good. You don't seem to understand just how useful they are. These are what Intellisense picks up on to provide tool-tips on what various parameters mean/do. Therefore, if you wish to show Intellisense tool-tips properly, then they are a must. \$\endgroup\$Der Kommissar– Der Kommissar2015年09月29日 18:11:45 +00:00Commented Sep 29, 2015 at 18:11
-
\$\begingroup\$ I think these comments contains too much information. Take a look at the .Net framework, (which is my reference, might not be the good one though), the comments are well written, but don't give whole exposition on the method/field etc. \$\endgroup\$IEatBagels– IEatBagels2015年09月29日 18:19:52 +00:00Commented Sep 29, 2015 at 18:19
-
\$\begingroup\$ It's not a useless comment. It tells me that there's an inconsistent implementation of Equals & == \$\endgroup\$RubberDuck– RubberDuck2015年10月03日 09:12:35 +00:00Commented Oct 3, 2015 at 9:12
bool?
in your comments, but you don't actually need thenullity
operator. The comments should saybool
instead. \$\endgroup\$