I am upgrading an existing application that has implemented a home-brew Constants class in its business and datalayer objects.
I want to replace this with Nullable types and do-away with the constants class, that looks like this, but with all non-nullable data types:
class Constants
{
public static int nullInt
{
get { return int.MinValue; }
}
}
These constants vaules are used as defaults on almost all the object properties like this:
private decimal _unitPrice = Constants.nullInt;
public decimal UnitPrice
{
get { return _unitPrice; }
set { _unitPrice = (value == null) ? Constants.nullInt : value; }
}
This causes some confusion on saving object properties to the Db as all decimal's and ints have to be checked for psudo null values or else you save things like int.MinValue to the Db.
private void Save()
{
//Datalayer calls and other props omitted
SqlParameter sqlParm = new SqlParameter();
sqlParm.Value = (this.UnitPrice == Constants.nullInt) ? DBNull.Value : (object)this.UnitPrice;
}
Ok so now the question.. I want to change things around using Nullable value types as in my example below, will the change in a property from a decimal to a decimal? affect any code thats implementing these objects?
public decimal? UnitPrice { get; set; }
private void Save()
{
//Datalayer calls and other props omitted
SqlParameter sqlParm = new SqlParameter();
sqlParm.Value = this.UnitPrice ?? DBNull.Value;
}
EDIT: Thanks for the double check of my refactor, and yes the null check on the SET of the property in the original code would be redundant. I still want to know if code that implements this object could have any issues from the change of type to decimal? from decimal
public decimal? UnitPrice { get; set; }
private void Save()
{
//Datalayer calls and other props omitted
SqlParameter sqlParm = new SqlParameter();
sqlParm.Value = this.UnitPrice ?? DBNull.Value;
}
I find this absolutely ok. This is how it is supposed to work.
I'm a little unclear as to the implementation of your above property set method as a decimal can never be null so this test is redundant I think. I'm including some sample code that can be dropped into a Console application that should clear things up for you.
You will experience very little refactoring of your code due to switching over to the nullable data types. This will be a good move on your part in cleaning up your code and avoiding the potential pitfalls of your current implementation.
If nothing else, the performance gains you'll receive would likely make the effort worth your while. Nothing will be completely plug-n-play so to speak but if you look at the below code you'll see there's very little impact in the scenarios you've provided.
Example
using System;
using System.Data.SqlClient;
namespace NullableTypes
{
class Program
{
static class Constants
{
public static decimal NullDecimal
{
get { return decimal.MinValue; }
}
}
public class ProductTheOldWay
{
public string Name { get; set; }
public decimal UnitPrice { get; set; }
public ProductTheOldWay()
{
Name = string.Empty;
UnitPrice = Constants.NullDecimal;
}
public override string ToString()
{
return "Product: " + Name + " Price: " +
((UnitPrice == Constants.NullDecimal)
? "Out of stock"
: UnitPrice.ToString());
}
public void Save()
{
//Datalayer calls and other props omitted
var sqlParm = new SqlParameter
{
Value = (UnitPrice == Constants.NullDecimal)
? DBNull.Value
: (object)UnitPrice
};
//save to the database...
Console.WriteLine("Value written to the database: " + sqlParm.Value);
}
}
public class ProductTheNewWay
{
public string Name { get; set; }
public decimal? UnitPrice { get; set; }
public ProductTheNewWay()
{
Name = string.Empty;
}
public override string ToString()
{
return "Product: " + Name + " Price: " +
((UnitPrice.HasValue)
? UnitPrice.ToString()
: "Out of stock");
}
public void Save()
{
//Datalayer calls and other props omitted
var sqlParm = new SqlParameter
{
Value = UnitPrice
};
//save to the database...
Console.WriteLine("Value written to the database: " + sqlParm.Value);
}
}
static void Main()
{
var oldProduct1 = new ProductTheOldWay
{
Name = "Widget",
UnitPrice = 5.99M
};
var oldProduct2 = new ProductTheOldWay
{
Name = "Rare Widget",
UnitPrice = Constants.NullDecimal // out of stock
};
Console.WriteLine(oldProduct1);
Console.WriteLine(oldProduct2);
Console.WriteLine("Saving...");
oldProduct1.Save();
oldProduct2.Save();
Console.ReadLine();
var newProduct1 = new ProductTheNewWay
{
Name = "Widget",
UnitPrice = 5.99M
};
var newProduct2 = new ProductTheNewWay
{
Name = "Rare Widget"
/* UnitPrice = null by default */
};
Console.WriteLine(newProduct1);
Console.WriteLine(newProduct2);
Console.WriteLine("Saving...");
newProduct1.Save();
newProduct2.Save();
Console.ReadLine();
// as a further example of the new property usage..
if (newProduct1.UnitPrice > 5)
Console.WriteLine(newProduct1);
Console.WriteLine("Using nullable types is a great way to simplify code...");
Console.ReadLine();
}
}
}
Output
Product: Widget Price: 5.99
Product: Rare Widget Price: Out of stock
Saving...
Value written to the database: 5.99
Value written to the database:
Product: Widget Price: 5.99
Product: Rare Widget Price: Out of stock
Saving...
Value written to the database: 5.99
Value written to the database:
Product: Widget Price: 5.99
Using nullable data types is a great way to simplify code...
Let me know if there are more specific implementation details that concern you making the switch.
Related
class Program
{
public static void Main(string[] args)
{
Car c1 ;
// c1's data
c1.Brand = "Bugatti";
c1.Model = "Bugatti Veyron EB 16.4";
c1.Color = "Gray";
// c1's data
// Displaying the values
Console.WriteLine(//"Name of brand: " + c1.Brand +
"\nModel name: " + c1.Model +
"\nColor of car: " + c1.Color);
Console.ReadLine();
}
}
public struct Car
{
// Declaring different data types
public string Brand { get; set; }
public string Model;
public string Color;
}
c1.Brand ="Bugatti" // giving compilation error - use of unassigned local variable 'c1'
//local variable might not be initialized before accessing
but c.Model or c1.Color is not throwing error.
As per the documentation:
Because structure types have value semantics, we recommend you to define immutable structure types.
Option 1
If your Car type's properties don't need to change at runtime, they should not have any public setters. You can simplify population of a new Car by adding a constructor or by using one of the other options described in the docs.
static class Program
{
static void Main(string[] args)
{
Car c1 = new Car(
// c1's data
brand: "Bugatti",
model: "Bugatti Veyron EB 16.4",
color: "Gray");
// c1's data
// Displaying the values
Console.WriteLine(//"Name of brand: " + c1.Brand +
"\nModel name: " + c1.Model +
"\nColor of car: " + c1.Color);
Console.ReadLine();
}
}
public struct Car
{
public Car(string brand, string model, string color)
{
Brand = brand ?? throw new ArgumentNullException(nameof(brand));
Model = model ?? throw new ArgumentNullException(nameof(model));
Color = color ?? throw new ArgumentNullException(nameof(color));
}
// Declaring different data types
public string Brand { get;}
public string Model { get; }
public string Color { get; }
}
Option 2
Use a class instead of a struct.
static void Main(string[] args)
{
Car c1 = new Car(
// c1's data
model: "Bugatti Veyron EB 16.4",
color: "Gray")
{ Brand = "Bugatti" };
// c1's data
// Displaying the values
Console.WriteLine(//"Name of brand: " + c1.Brand +
"\nModel name: " + c1.Model +
"\nColor of car: " + c1.Color);
Console.ReadLine();
}
}
public class Car
{
public Car(string model, string color)
{
Model = model ?? throw new ArgumentNullException(nameof(model));
Color = color ?? throw new ArgumentNullException(nameof(color));
}
// Declaring different data types
public string Brand { get; set; }
public string Model { get; }
public string Color { get; }
}
Since one of the main benefits of using struct is to allocate fixed sizes of data on the stack (using stackalloc) and you are using string properties (which are not a fixed size), the most sensible option is to use a class for this scenario. Whether you actually need a constructor, use property setters, or logical defaults depends on whether your application will deal with the values if they are null and/or default.
I have the following code:
namespace QuantStrats
{
class Program
{
static void Main(string[] args)
{
string FilePath = "C:\\Users\\files\\DJ.csv";
StreamReader streamReader = new StreamReader(FilePath);
string line;
List<Data> Data = new List<Data>();
while ((line = streamReader.ReadLine()) != null)
{
Data Tick = new Data();
string [] values = line.Split(',');
Tick.SetFields(values[1], values[2]);
Data.Add(Tick);
}
for (int ii = 0; ii < Data.Count; ii++)
{
Data TickDataValues = new Data();
TickDataValues = Data[ii];
Console.Write("TIME :" + TickDataValues.time + " Price : " + TickDataValues.price + Environment.NewLine);
}
Console.ReadLine();
}
}
class Data
{
public DateTime time
{
get { return this.time; }
set
{
this.time = value;
}
}
public double price
{
get { return this.price; }
set
{
this.price = value;
}
}
public void SetFields(string dateTimeValue, string PriceValue)
{
try
{
this.time = Convert.ToDateTime(dateTimeValue);
}
catch
{
Console.WriteLine("DateTimeFailed " + dateTimeValue + Environment.NewLine);
}
try
{
this.price = Convert.ToDouble(PriceValue);
}
catch
{
Console.WriteLine("PriceFailed " + PriceValue + Environment.NewLine);
}
}
}
}
But I get a stack overflow exception.
I know it is because I am not doing my get and sets correctly and am entering an infinite loop, but I cannot see why exactly this is happening?
public DateTime time
{
get { return this.time; }
set
{
this.time = value;
}
}
you aren't using backing fields, but setting the property itself from within the property setter.
You can fix this by using 1) an auto property
public DateTime Time { get; set; }
or 2) a backing field
private DateTime _time;
public Datetime Time
{
get { return _time; }
set { _time = value; }
}
they both equate to the same code.
For an explanation, when you get time in your code:
get { return this.time; }
it has to retrieve the value of time to return. It does that by calling the get on time, which has to get retrieve the value of time, etc.
I cannot see why exactly this is happening?
public double price
{
get { return this.price; }
set
{
this.price = value;
}
}
When you "get" price, the getter for price is called, which calls the getter for price, which calls the getter for price, which...
Just use auto-implement properties if you don't want to mess with a backing field:
public DateTime Time {get; set;}
public double Price {get; set;}
Some other observations:
The standard convention for property names is to start them with a capital letter, which is why I changed your properties to Time and Price in my examples.
You may want to consider using decimal for a property like Price if you do any floating-point math, since double has some slight imprecision when representing decimal numbers like 1.1. decimal will store the number exacly without any loss of precision.
Just writing to the console in a catch block seems incorrect. You are basically ignoring the error (from a logic flow sense). Rather than accepting strings in the class and parsing them, I would do the validation in the calling code and making sure the inputs are valid before passing them to the class.
Properties getters and setters are really just getXXX and setXXX methods (that's how they are compiled). Because you set the property from the property itself, it is if you were recurring endlessly on a method.
public DateTime time()
{
return time();
}
As stated by other answers, you can use backing fields or auto-implemented properties.
I would like to save in the database a custom type property which is a class.
The property to save is myProperty with the type CustomTypeClass in the below example:
public class Test
{
private CustomTypeClass myProperty;
public CustomTypeClass MyProperty
{
get { return myProperty; }
set { myProperty = value; }
}
}
So, the value to save in the database is the result of the ToString override method:
public class CustomType
{
public int Start { get; set; }
public int End { get; set; }
public CustomType(int start, int end)
{
this.Start = start;
this.End = end;
}
public override string ToString()
{
return "[" + this.Start + "," + this.End + "]";
}
}
I would like to perform the following code for saving in the database:
CustomType value = new CustomType(3, 5);
Test test = new Test();
test.myProperty = value;
database.Add(test);
database.SaveChanges();
How can I do this ?
At the moment, I get the following error:
Unsupported field-type 'CustomTypeClass' found for field 'myProperty' of class 'Test'.
Maybe you did not specify the TransientAttribute for this field or the PersistentAttribute is not specified for the referenced class. [class=Test]
I don't think you'll be able to do that since it's not a known attribute within the database. However, What I've done in the past is something like:
//Save logic:
CustomType value = new CustomType(3, 5);
Test test = new Test();
test.myProperty = value.ToString();
database.Add(test);
database.SaveChanges();
And then create a static method on CustomType to convert it from a string back to an object:
var obj = db.Tests.First();
var customType = CustomType.LoadFromString(obj.myProperty);
Where LoadFromString is a static method on the object and converts it from a string back into an object (by parsing the string).
In my Class i need to set one property value according to another:
public class Quantities
{
private int _quant;
public int Quant
{
get { return _quant; }
set
{
if (Unit == "K")
{
_quant = value / 1000;
}
else
{
_quant = value;
}
}
}
public string Unit { get; set; }
}
according to several tests i made it works fine but i still don't know if it's safe to do this.
is it possible that the Quant Property will be evaluated before the Unit Property or does the compiler (or JIT) knows that it should assign the Unit Property first?
This has nothing to do with the compiler or the JIT. Your code assigns the values. You need to know the order in which they should be assigned.
BTW: Your code exhibits temporal coupling. It would be better to make at least the Unit unchangeable by making the property readonly and by providing a constructor that requires the unit:
public class Quantities
{
private readonly string _unit;
private int _quant;
public Quantities(string unit)
{
if(unit == null) throw new ArgumentNullException("unit");
_unit = unit;
}
public int Quant
{
get { return _quant; }
set
{
if (Unit == "K")
{
_quant = value / 1000;
}
else
{
_quant = value;
}
}
}
public string Unit { get { return _unit; } }
}
This class now can't be used in an incorrect way.
For more points that can be improved with your class, please refer to Lasse's answer.
Code on the outside of this class must know about this dependency or you risk someone changing Unit without re-setting Quant:
var x = new Quantities(); // why no constructor for this?
x.Unit = "K";
x.Quant = 1700; // why int? this will now be 1, not 1.7
x.Unit = "M";
Personally I would make the class a struct, and make it immutable:
public struct Quantity
{
private readonly double _Value;
private readonly string _Unit;
public Quantity(double value, string unit)
{
_Value = value;
_Unit = unit;
}
public double Value
{
{
return _Value;
}
}
public double Unit
{
{
return _Unit;
}
}
}
Also note that I did not change the value at all, hence:
var x = new Quantity(1700, "K");
means 1700K, not 1.7K. I would refrain from doing such "automagical" interpretations of data. If you need to display the value with a different unit, I would instead build in a conversion system:
public Quantity ConvertToUnit(string newUnit)
{
var newValue = ... calculate value with new unit
return new Quantity(newValue, newUnit);
}
The class is not a good design. Do not do this.
Consider the following code:
Quantities q1 = new Quantities { Unit = "K", Quant = 1000};
Console.WriteLine(q1.Quant); // Prints 1
// Make a copy of q1
Quantities q2 = new Quantities{ Unit = q1.Unit, Quant = q1.Quant };
Console.WriteLine(q2.Quant); // Prints 0
You would expect that making a copy of the Quantities would work by doing a basic copy like the above. That it does not shows you how dangerous this kind of design is.
This is still a problem after making the changes in the accepted answer above
If you use the changes that Daniel suggested, you still have the nastyness associated with your property setter and getter not being commutative. Sure, you would be forced to pass the units into the constructor, but the object copy still won't work as the user might expect:
Quantities q1 = new Quantities("K"){ Quant = 1000};
Console.WriteLine(q1.Quant); // Prints 1
// Make a copy of q1
Quantities q2 = new Quantities(q1.Unit){ Quant = q1.Quant };
Console.WriteLine(q2.Quant); // STILL Prints 0
I've got something like this in my property/accessor method of a constructor for my program.
using System;
namespace BusinessTrips
{
public class Expense
{
private string paymentMethod;
public Expense()
{
}
public Expense(string pmtMthd)
{
paymentMethod = pmtMthd;
}
//This is where things get problematic
public string PaymentMethod
{
get
{
return paymentMethod;
}
set
{
if (string.IsNullOrWhiteSpace(" "))
paymentMethod = "~~unspecified~~";
else paymentMethod = value;
}
}
}
}
When a new attribute is entered, for PaymentMethod, which is null or a space, this clearly does not work. Any ideas?
do you perhaps just need to replace string.IsNullOrWhiteSpace(" ") with string.IsNullOrWhiteSpace(value) ?
From your posted code, you need to call:
this.PaymentMethod = pmtMthd;
instead of
paymentMethod = pmtMthd;
The capital p will use your property instead of the string directly. This is why it's a good idea to use this. when accessing class variables. In this case, it's the capital not the this. that makes the difference, but I'd get into the habit of using this.
Jean-Barnard Pellerin's answer is correct.
But here is the full code, which I tested in LinqPad to show that it works.
public class Foo {
private string _paymentMethod = "~~unspecified~~";
public string PaymentMethod
{
get
{
return _paymentMethod;
}
set
{
if (string.IsNullOrWhiteSpace(value))
_paymentMethod = "~~unspecified~~";
else _paymentMethod = value;
}
}
}
With a main of:
void Main()
{
var f = new Foo();
f.PaymentMethod = "";
Console.WriteLine(f.PaymentMethod);
f.PaymentMethod = " ";
Console.WriteLine(f.PaymentMethod);
f.PaymentMethod = "FooBar";
Console.WriteLine(f.PaymentMethod);
}
Output from console:
~~unspecified~~
~~unspecified~~
FooBar