Skip to content

Commit

Permalink
2X faster assignment for nullable types
Browse files Browse the repository at this point in the history
  • Loading branch information
alex-jitbit committed May 5, 2023
1 parent ab0378c commit 5aecb6c
Show file tree
Hide file tree
Showing 3 changed files with 45 additions and 9 deletions.
39 changes: 31 additions & 8 deletions MapDataReader.Benchmarks/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@ public class Benchy
{
static TestClass _o = new TestClass();
static PropertyInfo _prop = _o.GetType().GetProperty("String1", BindingFlags.Public | BindingFlags.Instance);
static PropertyInfo _nullableprop = _o.GetType().GetProperty("IntNullable", BindingFlags.Public | BindingFlags.Instance);

[Benchmark]
public void SetPropReflection()
public void SetProp_Reflection()
{
PropertyInfo prop = _o.GetType().GetProperty("String1", BindingFlags.Public | BindingFlags.Instance);
if (null != prop && prop.CanWrite)
Expand All @@ -31,26 +32,38 @@ public void SetPropReflection()
}

[Benchmark]
public void SetPropReflectionCached()
public void SetProp_ReflectionCached()
{
_prop.SetValue(_o, "Value");
}

[Benchmark]
public void SetPropMapDataReader()
public void SetProp_MapDataReader()
{
_o.SetPropertyByName("String1", "Value");
}


[Benchmark]
public void SetNullableProp_ReflectionCached()
{
_nullableprop.SetValue(_o, 123);
}

[Benchmark]
public void MapDatareaderViaDapper()
public void SetNullableProp_MapDataReader()
{
_o.SetPropertyByName("IntNullable", 123);
}

[Benchmark]
public void MapDatareader_ViaDapper()
{
var dr = _dt.CreateDataReader();
var list = dr.Parse<TestClass>().ToList();
}

[Benchmark]
public void MapDataReaderViaMapaDataReader()
public void MapDataReader_ViaMapaDataReader()
{
var dr = _dt.CreateDataReader();
var list = dr.ToTestClass();
Expand All @@ -63,10 +76,19 @@ public static void Setup()
{
//create datatable with test data
_dt = new DataTable();
_dt.Columns.AddRange(new[] { new DataColumn("String1", typeof(string)), new DataColumn("String2", typeof(string)), new DataColumn("String3", typeof(string)), new DataColumn("Int", typeof(int)), new DataColumn("Int2", typeof(int)) });
_dt.Columns.AddRange(new[] {
new DataColumn("String1", typeof(string)),
new DataColumn("String2", typeof(string)),
new DataColumn("String3", typeof(string)),
new DataColumn("Int", typeof(int)),
new DataColumn("Int2", typeof(int)),
new DataColumn("IntNullable", typeof(int))
});


for (int i = 0; i < 1000; i++)
{
_dt.Rows.Add("xxx", "yyy", "zzz", 123, 321);
_dt.Rows.Add("xxx", "yyy", "zzz", 123, 321, 3211);
}
}
}
Expand All @@ -79,5 +101,6 @@ public class TestClass
public string String3 { get; set; }
public string Int { get; set; }
public string Int2 { get; set; }
public int? IntNullable { get; set; }
}
}
6 changes: 6 additions & 0 deletions MapDataReader.Tests/TestActualCode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,12 @@ public void TestPrimitiveTypesAssign()
o.SetPropertyByName("NUllableBirthDay", dt);
Assert.IsTrue(o.NUllableBirthDay == dt);

//test nullable assign
DateTime? ndt = null;
ndt = new DateTime(2022, 10, 10);
o.SetPropertyByName("NUllableBirthDay", ndt);
Assert.IsTrue(o.NUllableBirthDay == ndt);

o.SetPropertyByName("GetOnly", 321); //should not throw any exception, even though this property is not settable

o.SetPropertyByName("ByeArray", new byte[3] { 1, 2, 3 });
Expand Down
9 changes: 8 additions & 1 deletion MapDataReader/MapperGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,17 @@ private static void SetPropertyByUpperName(this {typeNodeSymbol.FullName()} targ
{
var pTypeName = p.Type.FullName();
if (p.Type.IsReferenceType || (pTypeName.EndsWith("?") && !p.Type.IsNullableEnum())) //ref types and nullable type - just cast to property type (unless nullable Enum)
if (p.Type.IsReferenceType) //ref types - just cast to property type
{
return $@" if (name == ""{p.Name.ToUpperInvariant()}"") {{ target.{p.Name} = value as {pTypeName}; return; }}";
}
else if (pTypeName.EndsWith("?") && !p.Type.IsNullableEnum()) //nullable type (unless nullable Enum)
{
var nonNullableTypeName = pTypeName.TrimEnd('?');
//do not use "as" operator becasue "as" is slow for nullable types. Use "is" and a null-check
return $@" if (name == ""{p.Name.ToUpperInvariant()}"") {{ if(value==null) target.{p.Name}=null; else if(value is {nonNullableTypeName}) target.{p.Name}=({nonNullableTypeName})value; return; }}";
}
else if (p.Type.TypeKind == TypeKind.Enum || p.Type.IsNullableEnum()) //enum? pre-convert to underlying type then to int, you can't cast a boxed int to enum directly. Also to support assigning "smallint" database col to int32 (for example), which does not work at first (you can't cast a boxed "byte" to "int")
{
return $@" if (value != null && name == ""{p.Name.ToUpperInvariant()}"") {{ target.{p.Name} = ({pTypeName})(value.GetType() == typeof(int) ? (int)value : (int)Convert.ChangeType(value, typeof(int))); return; }}"; //pre-convert enums to int first (after unboxing, see below)
Expand Down

0 comments on commit 5aecb6c

Please sign in to comment.