I have a class with many fields which represents different physical values.
class Tunnel
{
private double _length;
private double _crossSectionArea;
private double _airDensity;
//...
Each field is exposed using read/write property. I need to check on setter that the value is correct and generate exception otherwise. All validations are similar:
public double Length
{
get { return _length; }
set
{
if (value <= 0) throw new ArgumentOutOfRangeException("value",
"Length must be positive value.");
_length = value;
}
}
public double CrossSectionArea
{
get { return _crossSectionArea; }
set
{
if (value <= 0) throw new ArgumentOutOfRangeException("value",
"Cross-section area must be positive value.");
_crossSectionArea = value;
}
}
public double AirDensity
{
get { return _airDensity; }
set
{
if (value < 0) throw new ArgumentOutOfRangeException("value",
"Air density can't be negative value.");
_airDensity = value;
}
}
//...
Is there any elegant and flexible way to accomplish such validation?
Assuming you want this sort of behaviour, you might consider some helper methods, e.g.
public static double ValidatePositive(double input, string name)
{
if (input <= 0)
{
throw new ArgumentOutOfRangeException(name + " must be positive");
}
return input;
}
public static double ValidateNonNegative(double input, string name)
{
if (input < 0)
{
throw new ArgumentOutOfRangeException(name + " must not be negative");
}
return input;
}
Then you can write:
public double AirDensity
{
get { return _airDensity; }
set
{
_airDensity = ValidationHelpers.ValidateNonNegative(value,
"Air density");
}
}
If you need this for various types, you could even make it generic:
public static T ValidateNonNegative(T input, string name)
where T : IComparable<T>
{
if (input.CompareTo(default(T)) < 0)
{
throw new ArgumentOutOfRangeException(name + " must not be negative");
}
return input;
}
Note that none of this is terribly i18n-friendly...
All depends what technology you are using - if you're under MVC you can use Attributes, like this;
http://msdn.microsoft.com/en-us/library/ee256141(v=vs.98).aspx
Here's my version, it's a bit cleaner than Jon's version in some respects:
interface IValidator <T>
{
bool Validate (T value);
}
class IntValidator : IValidator <int>
{
public bool Validate (int value)
{
return value > 10 && value < 15;
}
}
class Int2Validator : IValidator<int>
{
public bool Validate (int value)
{
return value > 100 && value < 150;
}
}
struct Property<T, P> where P : IValidator<T>, new ()
{
public T Value
{
set
{
if (m_validator.Validate (value))
{
m_value = value;
}
else
{
Console.WriteLine ("Error validating: '" + value + "' is out of range.");
}
}
get { return m_value; }
}
T m_value;
static IValidator<T> m_validator=new P();
}
class Program
{
static void Main (string [] args)
{
Program
p = new Program ();
p.m_p1.Value = 9;
p.m_p1.Value = 12;
p.m_p1.Value = 25;
p.m_p2.Value = 90;
p.m_p2.Value = 120;
p.m_p2.Value = 250;
}
Property<int, IntValidator>
m_p1;
Property<int, Int2Validator>
m_p2;
}
Try to use such a method:
public void FailOrProceed(Func<bool> validationFunction, Action proceedFunction, string errorMessage)
{
// !!! check for nulls, etc
if (!validationFunction())
{
throw new ArgumentOutOfRangeException(errorMessage);
}
proceedFunction();
}
Yes, by creating your own validation attributes.
Read this article: Business Object Validation Using Attributes in C#
I will have the decency of NOT copying it here :)
Using the Validator function I mentioned in my comment above, I'd do something like this (untested code):
void textBox_Changed(object sender, EventArgs e) {
submitButton.Enabled = validator();
}
bool validator() {
const string NON_POSITIVE = "Value must be greater than Zero";
bool result = false;
string controlName = "Length";
try {
_length = Convert.ToDouble(txtLength.Text);
if (_length <= 0) throw new Exception(NON_POSITIVE);
controlName = "Cross Section Area";
_crossSectionArea = Convert.ToDouble(txtCrossSectionArea.Text);
if (_crossSectionArea <= 0) throw new Exception(NON_POSITIVE);
controlName = "Air Density";
_airDensity = Convert.ToDouble(txtAirDensity.Text);
if (_airDensity <= 0) throw new Exception(NON_POSITIVE);
result = true; // only do this step last
} catch (Exception err) {
MessageBox.Show(controlName + " Error: " + err.Message, "Input Error");
}
return result;
}
John Skeet probably has a better way, but this works. :)
You can achieve this using classes from System.ComponentModel.DataAnnotations
class Tunnel
{
[Range(0, double.MaxValue, ErrorMessage = "Length must be positive value.")]
public double Length { get; set; }
}
Validation:
var tunnel = new Tunnel { Length = 0 };
var context = new ValidationContext(tunnel, null, null);
Validator.ValidateObject(tunnel, context, true);
Also you can implement your own validation attributes overriding ValidationAttribute class
Related
using System;
namespace ConsoleApp12
{
class Cake
{
private string cakeType;
private int weight;
private bool baked;
public Cake(string cakeType, int weight)
{
this.cakeType = cakeType;
this.weight = weight;
this.baked = true;
}
public Cake(string cakeType, int weight, bool baked)
{
this.cakeType = cakeType;
this.weight = weight;
this.baked = baked;
}
public Cake(int weight)
{
this.cakeType = "chocolate";
this.weight = weight;
this.baked = true;
}
public string IsBaked()
{
int count = 0;
Cake[] CakeArr = new Cake[3];
for (int i = 0; i < CakeArr.Length; i++)
{
if (/* if cake is baked */)
{
return "The Cake is indeed baked...";
}
else
{
count++;
}
}
return "There are: " + count + " not baked cakes...";
}
public string GetCakeType() { return this.cakeType; }
public int GetWeight() { return this.weight; }
public bool GetBaked() { return this.baked; }
public void SetBaked(bool baked) { this.baked = baked; }
public void Sold(int weight) { this.weight -= weight; }
}
class Program
{
static void Main(string[] args)
{
Cake c1 = new Cake(1800);
Cake c2 = new Cake("cheese", 1200, false);
Cake c3 = new Cake("chocolate", 2100, true);
c1.Sold(400);
if (c1 == c3)
Console.WriteLine("aaa");
else
Console.WriteLine("bbb");
c3.Sold(700);
if (c1 == c2 || c2 == c3)
Console.WriteLine("ccc");
else
if (c1 == c3)
Console.WriteLine("ddd");
c1.Sold(1400);
c2.SetBaked(c1.GetBaked());
if (c2.GetBaked())
Console.WriteLine("eee");
Console.WriteLine(c2.IsBaked());
}
}
}
I did everything I need but I do not know how to make the system check if the cake is baked in an if statement...
It doesnt matter what everything else do like the other methods because they all supposed to work anyways I just need to find a solution to the method IsBaked in the class Cake.
Please help, thank you.
Assuming you want to check how many of c1, c2, c3 are baked, you might want to create an extension method:
public static class MyCakeExtensionMethods{
public static string HowManyAreNotBaked(this Cake[] cakeArr ){
int count = 0;
for (int i = 0; i < cakeArr.Length; i++)
{
if (!cakeArr[i].GetBaked())
{
count++;
}
}
if( count == 0){
return "all cakes are baked";
return "There are: " + count + " not baked cakes...";
}
}
Note that the body could be mostly simplified to CakeArr.Count(c => c.GetBaked());
But there are other issues with your code:
You should probably be using properties instead of get/set methods
You should probably not allow the cake type to be changed after creation. Unless you found some way to transform a cheese-cake into a chocolatecake in the real world.
You should probably change SetBaked(bool baked) into Bake(), since you cannot un-bake a cake.
You should probably add a check to Sold to ensure you cannot sell more cake than there is.
Assuming I have the following extension method:
public static string sampleMethod(this int num) {
return "Valid";
}
how can I terminate sampleMethod and show a messagebox if num > 25 ?
If I try below code,I receive a red underline on the sampleMethod and says not all code path returns a value.
public static string sampleMethod(this int num) {
if(num > 25) {
MessageBox.Show("Integer must not exceed 25 !");
} else {
return "Valid String";
}
}
and if I add throw new Exception("..."); under the MessageBox.Show, everything goes well but the application terminates.
how can I show the MessageBox and terminate the Method instead if the condition is not met ?
Thank you.
Make sure you always return a string (since string is your return value) to all possible outcome/path of your function
public static string sampleMethod(this int num) {
if(num > 25) {
MessageBox.Show("Integer must not exceed 25 !");
return "";
}
return "Valid String";
}
your code didn't work because
public static string sampleMethod(this int num) {
if(num > 25) {
MessageBox.Show("Integer must not exceed 25 !");
// when it go to this block, it is not returning anything
} else {
return "Valid String";
}
}
Suppose you have array of strings with 25 indexes:
public String[] data = new String[25] { /* declare strings here, e.g. "Lorem Ipsum" */ }
// indexer
public String this [int num]
{
get
{
return data[num];
}
set
{
data[num] = value;
}
}
The method should be changed as below if you not want to return any string when the array index exceeds 25:
public static String sampleMethod(this int num) {
if(num > 25) {
MessageBox.Show("Integer must not exceed 25 !");
return String.Empty; // this won't provide any string value
} else {
return "Valid String";
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections.Concurrent;
namespace Crystal_Message
{
class Message
{
private int messageID;
private string message;
private ConcurrentBag <Employee> messageFor;
private Person messageFrom;
private string calltype;
public Message(int iden,string message, Person messageFrom, string calltype, string telephone)
{
this.messageID = iden;
this.messageFor = new ConcurrentBag<Employee>();
this.Note = message;
this.MessageFrom = messageFrom;
this.CallType = calltype;
}
public ConcurrentBag<Employee> ReturnMessageFor
{
get
{
return messageFor;
}
}
public int MessageIdentification
{
get { return this.messageID; }
private set
{
if(value == 0)
{
throw new ArgumentNullException("Must have Message ID");
}
this.messageID = value;
}
}
public string Note
{
get { return message; }
private set
{
if (string.IsNullOrWhiteSpace(value))
{
throw new ArgumentException("Must Have a Message");
}
this.message = value;
}
}
public Person MessageFrom
{
get { return messageFrom; }
private set
{
this.messageFrom = value;
}
}
public string CallType
{
get { return this.calltype; }
private set
{
if (string.IsNullOrWhiteSpace(value))
{
throw new ArgumentNullException("Please specify call type");
}
this.calltype = value;
}
}
public void addEmployee(Employee add)
{
messageFor.Add(add);
}
public override string ToString()
{
return "Message: " + this.message + " From: " + this.messageFrom + " Call Type: " + this.calltype + " For: " + this.returnMessagefor();
}
private string returnMessagefor()
{
string generate="";
foreach(Employee view in messageFor)
{
generate += view.ToString() + " ";
}
return generate;
}
public override bool Equals(object obj)
{
if (obj == null)
{
return false;
}
Message testEquals = obj as Message;
if((System.Object)testEquals == null)
{
return false;
}
return (this.messageID == testEquals.messageID) && (this.message == testEquals.message) && (this.messageFor == testEquals.messageFor) && (this.messageFrom == testEquals.messageFrom) && (this.calltype == testEquals.calltype);
}
public bool Equals(Message p)
{
if ((Object)p == null)
{
return false;
}
return (this.messageID == p.messageID) && (this.message == p.message) && (this.messageFor == p.messageFor) && (this.messageFrom == p.messageFrom) && (this.calltype == p.calltype);
}
public override int GetHashCode()
{
unchecked
{
return this.messageID.GetHashCode() * 33 ^ this.message.GetHashCode() * 33 ^ this.messageFor.GetHashCode() * 33 ^ this.messageFrom.GetHashCode() * 33 ^ this.calltype.GetHashCode();
}
}
}
}
I have a Message class where a user could leave a message for more than one person. I have a getter for it, however, is returning a ConcurrentBag<> the way I've done proper practice? If not, how do i return the ConcurrentBag<> so I can loop through it and display it?
ConcurrentBag<T> is an IEnumerable<T>. You can loop through it as usual. However, as this is a thread safe collection, there are performance concerns to using it.
If you want to get rid of the performance impact while looping, call ToArray on it and return the new array instead.
public IEnumerable<Employee> ReturnMessageFor
{
get
{
return messageFor.ToArray();
}
}
It's not clear to me what you are trying to accomplish.
Are you trying to externalize the Bag for all operations? Because that's what you did...
If you want to externalize something you can iterate over you should either return the Bag as IEnumerable or return an array or a list copied from the Bag.
Either way it's safe to iterate over. Might not be the best in terms of performance, but that's another question.
// Option 1
public IEnumerable<Employee> ReturnMessageFor
{
get
{
return messageFor;
}
}
// Option 2
public Employee[] ReturnMessageFor
{
get
{
return messageFor.ToArray();
}
}
Notes:
You might want to make messageFor readonly (in the code you posted it is readonly).
Remember that a ConcurrentBag allows you to safely iterate over a snapshot of the collection in a thread safe manner, but it does not lock the items in the collection.
I want to limit my string, so that you have to put a minimum of 3 chars and a max of 10 chars in. Is this possible in the following code below?
main.cs:
class Program
{
static void Main(string[] args)
{
Something hello = new Something();
string myname;
Something test = new Something();
myname = Console.ReadLine();
test.Name = myname;
}
}
class with properties:
class Okay : IYes
{
private string thename;
public string Name
{
get {return thename;}
set {thename = value;} //what to put here???
}
}
The setter is probably not the best place to check. You should make the check at the point of input:
string myname = "";
while (myname.Length<3 || myname.Length >10)
{
Console.WriteLine("Please enter your name (between 3 and 10 characters");
myname = Console.ReadLine();
}
test.Name = myname;
Obviously you can take some steps to make this more user friendly: maybe a different message after the first failure, some way of getting out of the loop, etc.
Try this:-
public string Naam
{
get { return thename; }
set
{
if (value.Length >= 3 && value.Length <= 10)
thename = value;
else
throw new ArgumentOutOfRangeException();
}
}
class Okay : IYes
{
private string name;
public string Name
{
get { return name; }
set
{
if (value == null) throw new ArgumentNullException("Name");
if (value.Length < 3 || value.Length > 10)
throw new ArgumentOutOfRangeException("Name");
name = value;
}
}
}
You can also truncate the string if it's too long, rather than throwing an exception by just taking (up to) the first 10 characters:
class Okay : IYes
{
private string name;
public string Name
{
get { return name; }
set
{
if (value == null) throw new ArgumentNullException("Name");
if (value.Length < 3) throw new ArgumentOutOfRangeException("Name");
name = string.Join("", value.Take(10));
}
}
}
private static void GenericTester()
{
Okay ok = new Okay {Name = "thisIsLongerThan10Characters"};
Console.WriteLine(ok.Name);
}
// Output:
// thisIsLong
What's the best way to do PowerShell cmdlet validation on dependent parameters? For example, in the sample cmdlet below I need to run validation that Low is greater than High but that doesn't seem to be possible with validation attributes.
[Cmdlet(VerbsCommon.Get, "FakeData")]
public class GetFakeData : PSCmdlet
{
[Parameter(Mandatory = true)]
[ValidateNotNullOrEmpty]
public int Low { get; set; }
[Parameter(Mandatory = true)]
[ValidateNotNullOrEmpty]
public int High { get; set; }
protected override void BeginProcessing()
{
if (Low >= High)
{
// Is there a better exception to throw here?
throw new CmdletInvocationException("Low must be less than High");
}
base.BeginProcessing();
}
protected override void OnProcessRecord()
{
// Do stuff...
}
}
Is there is a better way to do this? The main thing I don't like about the solution above is that I can't throw a ParameterBindingException like the validation attributes would do since it's an internal class. I could throw ArgumentException or PSArgumentException but those are really for Methods not cmdlets.
You need something like in the cmdlet get-random.
Because you can't use [validatescript()] attribute in a cmdlet 'cause it's valid only for powershell function/script at run-time you need to steal the idea from microsoft.powershell.utility\get-random:
The value check is done in the BeginProcessing() and use a customized error ThrowMinGreaterThanOrEqualMax
protected override void BeginProcessing()
{
using (GetRandomCommand.tracer.TraceMethod())
{
if (this.SetSeed.HasValue)
this.Generator = new Random(this.SetSeed.Value);
if (this.EffectiveParameterSet == GetRandomCommand.MyParameterSet.RandomNumber)
{
if (this.IsInt(this.Maximum) && this.IsInt(this.Minimum))
{
int minValue = this.ConvertToInt(this.Minimum, 0);
int maxValue = this.ConvertToInt(this.Maximum, int.MaxValue);
if (minValue >= maxValue)
this.ThrowMinGreaterThanOrEqualMax((object) minValue, (object) maxValue);
this.WriteObject((object) this.Generator.Next(minValue, maxValue));
}
else
{
double min = this.ConvertToDouble(this.Minimum, 0.0);
double max = this.ConvertToDouble(this.Maximum, double.MaxValue);
if (min >= max)
this.ThrowMinGreaterThanOrEqualMax((object) min, (object) max);
this.WriteObject((object) this.GetRandomDouble(min, max));
}
}
else
{
if (this.EffectiveParameterSet != GetRandomCommand.MyParameterSet.RandomListItem)
return;
this.chosenListItems = new List<object>();
this.numberOfProcessedListItems = 0;
if (this.Count != 0)
return;
this.Count = 1;
}
}
}
...
private void ThrowMinGreaterThanOrEqualMax(object min, object max)
{
if (min == null)
throw GetRandomCommand.tracer.NewArgumentNullException("min");
if (max == null)
throw GetRandomCommand.tracer.NewArgumentNullException("max");
string errorId = "MinGreaterThanOrEqualMax";
this.ThrowTerminatingError(new ErrorRecord((Exception) new ArgumentException(this.GetErrorDetails(errorId, min, max).Message), errorId, ErrorCategory.InvalidArgument, (object) null));
}
You can use a decompiler ( dotPeak ) to see the rest of the code to learn more on custom error for cmdlet