可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I am looking for possibilities to define a new type and using it in C# like below:
Class definition:
public class Position { public double180 Longitude { get; set; } // double180 is a type within a range -180 and 180 public double90 Latitude { get; set; } // double90 is a type within a range of -90 and 90 }
Usage:
var position = new Position { Longitude = 45, Latitude = 96 // This line should give an error while initializing the object };
回答1:
A type may be overkill, but if you want one, this is a good start:
struct Double180 : IEquatable<Double180> { private readonly double value; public Double180(double d) { if (d < -180 || d > 180) { throw new ArgumentOutOfRangeException("d"); } this.value = d; } public static implicit operator double(Double180 d) { return d.value; } public static explicit operator Double180(double d) { return new Double180(d); } public override string ToString() { return this.value.ToString(); } public bool Equals(Double180 other) { return this.value == other.value; } public override bool Equals(object obj) { return obj is Double180 && this.Equals((Double180)obj); } public override int GetHashCode() { return this.value.GetHashCode(); } public static bool operator ==(Double180 a, Double180 b) { return a.Equals(b); } public static bool operator !=(Double180 a, Double180 b) { return !a.Equals(b); } }
Of course, there are many more interfaces to implement, for example IConvertible and IComparable<Double180> would be nice.
As you can see, you know where this starts, but you don't know where it ends.
A setter validator, as suggested by the other answers, might be a better idea.
回答2:
You don't necessarily need a new type for this. Instead of using an auto property, you can manually write a setter which validates the value:
public double Latitude { get { return mLatitude; } set { if (value > 90 || value < -90) { throw new ArgumentOutOfRangeException("Invalid latitude"); } mLatitude = value; } } private double mLatitude;
If you want to reuse this code, you could define your own type and use the above setter in it; then provide an appropriate constructor and conversion operators.
回答3:
You would probably be better adding System.ComponentModel.DataAnnotations and using [Range] like so:
public class Position { [Range(-180, 180)] public double Longitude { get; set; } [Range(-90, 90)] public double Latitude { get; set; } }
回答4:
Use a double and have the setter check the value:
private double _longitude; public double Longitude { get { return _longitude; } set { if(value < -180 || value > 180) { throw new ArgumentException("value"); } _longitude = value; } }
回答5:
Add a validation step to the setter:
private double m_Latitude; public double Latitude { get{return m_Latitude;} set { if(value < -90 || value > 90) throw new ArgumentException("value"); m_Latitude = value; } }
Note that you as you are providing an implementation of the property you will need to add a member variable to store the underlying property value.
回答6:
I basically got the idea: validation the input inside the setter. When it comes to type definition, it seems Structs are simply the best. So finally, I will use below in my project.
public struct Coordinate { private readonly double _x; private readonly double _y; /// <summary> /// Longitude /// </summary> public double X { get { return _x; } } /// <summary> /// Latitude /// </summary> public double Y { get { return _y; } } /// <summary> /// Initiates a new coordinate. /// </summary> /// <param name="x">Longitude [-180, 180]</param> /// <param name="y">Latitude [-90, 90]</param> public Coordinate(double x, double y) { if (x < -180 || x > 180) throw new ArgumentOutOfRangeException( "x", "Longitude value must be in range of -180 and 180."); if (y < -90 || y > 90) throw new ArgumentOutOfRangeException( "y", "Latitude value must be in range of -90 and 90."); _x = x; _y = y; } }
Then I will use like this
var position = new Coordinate(46.32, 34.23);
Thank you all for your valuable comments.
回答7:
I like the documentation to be the part of a system:
public class Position { /// <summary> /// ... /// /// A value within a range -180 and 180 /// </summary> public double Longitude { get; set; } /// <summary> /// ... /// /// A value within a range -90 and 180 /// </summary> public double Latitude { get; set; } }
All dependent modules must be tested to comply with the specification of their dependency. Test-driven development is one way. Contract-driven development is another.
If you insist on "defencive programming" with run-time checks of values, then simply use a constructor:
public class Position { /// <summary> /// ... /// /// A value within a range -180 and 180 /// </summary> public double Longitude { get; private set; } /// <summary> /// ... /// /// A value within a range -90 and 180 /// </summary> public double Latitude { get; private set; } public Position(double longitude, double latitude) { if (longitude < -180 || longitude > 180) { throw new ArgumentOutOfRangeException(); } if (latitude < -90 || latitude > 90) { throw new ArgumentOutOfRangeException(); } Longitude = longitude; Latitude = latitude; } }
Or use a builder:
public class Position { public double Longitude { get; private set; } public double Latitude { get; private set; } /// <summary> /// Protects from invalid positions. Use <see cref="Position.Builder"/> /// </summary> private Position() { } /// <summary> /// Builds valid positions /// </summary> public class Builder { public double Longitude { get; set; } public double Latitude { get; set; } public Position Build() { if (Longitude < -180 || Longitude > 180) { throw new ArgumentOutOfRangeException(); } if (Latitude < -90 || Latitude > 90) { throw new ArgumentOutOfRangeException(); } return new Position() { Latitude = this.Latitude, Longitude = this.Longitude }; } } }
Usage:
Position p = new Position.Builder() { Latitude = 2, Longitude = 5 }.Build();
Summary:
- Run-time checks ("defensive programming"):
- Public setter with check (see other answers)
- Public constructor with check
- "Builder pattern" with builder performing checks
- Test-time checks:
- Test-driven
- Contract-driven