There's another option, that might be a bit combersome but will ensure the given ranges, is not to use double at all, but create a custom class called Grade. Your Grade class will contain casts from and to double, and will enforce validation.
This means that all Grade validation logic is in the Grade struct, and the Student class just receives a Grade object and doesn't have to worry about it.
public struct Grade
{
public static readonly double MinValue = 2.0;
public static readonly double MaxValue = 6.0;
private double value;
public static explicit operator double(Grade grade)
{
return grade.value + MinValue;
}
public static explicit operator Grade(double gradeValue)
{
if (gradeValue < MinValue || gradeValue > MaxValue)
throw new ArgumentOutOfRangeException("gradeValue", "Grade must be between 2.0 and 6.0");
return new Grade{ value = gradeValue - MinValue };
}
}
And you would use it like this:
double userInput = GetUserInputForGrade();
Grade grade = (Grade)userInput; // perform explicit cast.
Student student = new Student(firstName, lastName, grade);
Edit: I've updated the code with @EricLippert's suggestions, to make the class expose its min/max values to a developer.
Edit 2: Updated again with @JeppeStigNielsen's suggestion. The value field now stores an offset from MinValue, so that a call to default(Grade) will return a valid value (equal to MinValue) regardless of whether 0 is inside the valid range.