-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Leonard Sperry
committed
Jan 18, 2021
1 parent
b893797
commit 568a7fb
Showing
6 changed files
with
361 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
using Samples.Reflection; | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Text; | ||
using Xunit; | ||
|
||
namespace SampleTests.Reflection | ||
{ | ||
|
||
public class ExampleTests | ||
{ | ||
static ExampleTests() | ||
{ | ||
//initialize prior to test | ||
//var type = typeof(CsvGeneratorFast.CsvBuilder<TestModel>); | ||
//type.GetConstructors(); | ||
} | ||
public ExampleTests() | ||
{ | ||
} | ||
|
||
[Fact] | ||
public void TestSlow() | ||
{ | ||
CsvGeneratorSlow generator = new CsvGeneratorSlow(); | ||
|
||
var results = generator.OutputToCsv(GetTestData(), '|').ToArray(); | ||
} | ||
[Fact] | ||
public void TestSlow2() | ||
{ | ||
CsvGeneratorSlow generator = new CsvGeneratorSlow(); | ||
|
||
var results = generator.OutputToCsv(GetTestData(), '|').ToArray(); | ||
} | ||
|
||
[Fact] | ||
public void TestFast() | ||
{ | ||
CsvGeneratorFast generator = new CsvGeneratorFast(); | ||
|
||
var results = generator.OutputToCsv(GetTestData(), '|').ToArray(); | ||
} | ||
|
||
[Fact] | ||
public void TestFast2() | ||
{ | ||
CsvGeneratorFast generator = new CsvGeneratorFast(); | ||
|
||
var results = generator.OutputToCsv(GetTestData(), '|').ToArray(); | ||
} | ||
|
||
//[Fact] | ||
//public void TestWithException() | ||
//{ | ||
// CsvGeneratorFast generator = new CsvGeneratorFast(); | ||
|
||
// var results = generator.OutputToCsv(new ExceptionModel[] { new ExceptionModel()}).ToArray(); | ||
//} | ||
|
||
private IEnumerable<TestModel> GetTestData() | ||
{ | ||
const int count = 5000000; | ||
for (int i = 0; i < count; i++) | ||
{ | ||
yield return new TestModel(i, "Enterprise\"D") | ||
{ | ||
Captain = "James Kirk", | ||
Class = "Constitution" | ||
}; | ||
} | ||
} | ||
} | ||
|
||
class TestModel | ||
{ | ||
public int ID { get; set; } | ||
public string Name { get; set; } | ||
public DateTime LaunchDate { get; set; } | ||
public string Captain { get; set; } | ||
|
||
//public string Captain2 { get; set; } | ||
//public string Captain3 { get; set; } | ||
//public string Captain4 { get; set; } | ||
//public string Captain5 { get; set; } | ||
//public string Captain6 { get; set; } | ||
//public string Captain7 { get; set; } | ||
//public string Captain8 { get; set; } | ||
//public string Captain9 { get; set; } | ||
//public string Captain0 { get; set; } = "Jean Luc Picard"; | ||
//public string Captain01 { get; set; } = "Jean Luc Picard"; | ||
//public string Captain02 { get; set; } = "Jean Luc Picard"; | ||
//public string Captain03 { get; set; } = "Jean Luc Picard"; | ||
//public string Captain04 { get; set; } = "Jean Luc Picard"; | ||
//public string Captain05 { get; set; } = "Jean Luc Picard"; | ||
//public string Captain06 { get; set; } = "Jean Luc Picard"; | ||
//public string Captain07 { get; set; } = "Jean Luc Picard"; | ||
//public string Captain08 { get; set; } = "Jean Luc Picard"; | ||
//public string Captain09 { get; set; } = "Jean Luc Picard"; | ||
|
||
public string Class { get; set; } | ||
|
||
public TestModel(int id, string name) | ||
{ | ||
ID = id; | ||
Name = name; | ||
} | ||
|
||
public TestModel Custom { get { return new TestModel(1234,""); } } | ||
|
||
public override string ToString() | ||
{ | ||
return "Custom"; | ||
} | ||
} | ||
|
||
public class ExceptionModel | ||
{ | ||
public int MyProperty | ||
{ | ||
get | ||
{ | ||
throw new Exception(); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,162 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Linq.Expressions; | ||
using System.Reflection; | ||
|
||
namespace Samples.Reflection | ||
{ | ||
public interface ICsvGenerator | ||
{ | ||
IEnumerable<string> OutputToCsv<T>(IEnumerable<T> collection, char delimiter = ','); | ||
} | ||
|
||
public class CsvGeneratorSlow : ICsvGenerator | ||
{ | ||
public IEnumerable<string> OutputToCsv<T>( | ||
IEnumerable<T> collection, char delimiter = ',') | ||
{ | ||
var properties = typeof(T).GetProperties( | ||
BindingFlags.Public | BindingFlags.Instance) | ||
.Where(p => p.CanRead).ToArray(); | ||
|
||
yield return properties.Select(p => EscapeAndWrap(p.Name)) | ||
.Aggregate((p1, p2) => $"{p1}{delimiter}{p2}"); | ||
|
||
foreach (var item in collection) | ||
{ | ||
yield return | ||
properties.Select(p => EscapeAndWrap(GetValue(p, item))) | ||
.Aggregate((p1, p2) => $"{p1}{delimiter}{p2}"); | ||
} | ||
} | ||
|
||
private string GetValue<T>(PropertyInfo prop, T obj) | ||
{ | ||
try | ||
{ | ||
var val = prop.GetValue(obj); | ||
return val == null ? "null" : val.ToString(); | ||
} | ||
catch (Exception) | ||
{ | ||
return "error"; | ||
} | ||
} | ||
|
||
private static string EscapeAndWrap(string input) | ||
{ | ||
return input; | ||
return $"\"{input.Replace("\"", "\"\"")}\""; | ||
} | ||
|
||
} | ||
|
||
public class CsvGeneratorFast : ICsvGenerator | ||
{ | ||
static MethodInfo _toString; | ||
static MethodInfo _aggregate; | ||
|
||
static CsvGeneratorFast() | ||
{ | ||
_toString = typeof(object).GetMethod("ToString"); | ||
_aggregate = typeof(Enumerable).GetMethods() | ||
.First(m => m.Name == nameof(Enumerable.Aggregate) && m.GetGenericArguments().Length == 1).MakeGenericMethod(typeof(string)); | ||
} | ||
|
||
public IEnumerable<string> OutputToCsv<T>(IEnumerable<T> objects, char delimiter = ',') | ||
{ | ||
return CsvBuilder<T>.Actual(objects, delimiter); | ||
} | ||
|
||
private delegate string LineBuilder<T>(char delimiter, T item); | ||
private delegate string HeaderBuilder(char delimiter); | ||
|
||
private static class CsvBuilder<T> | ||
{ | ||
static LineBuilder<T> _lineBuilder; | ||
static HeaderBuilder _headerBuilder = null; | ||
|
||
static CsvBuilder() | ||
{ | ||
var properties = typeof(T).GetProperties( | ||
BindingFlags.Public | BindingFlags.Instance) | ||
.Where(p => p.CanRead).ToArray(); | ||
|
||
if (!properties.Any()) | ||
{ | ||
return; | ||
} | ||
|
||
_headerBuilder = delimiter => | ||
properties.Select(p => EscapeAndWrap(p.Name)) | ||
.Aggregate((s1, s2) => $"{s1}{delimiter}{s2}"); | ||
|
||
var valueGetters = | ||
properties.Select(prop => BuildPropertyGetter(prop)).ToArray(); | ||
|
||
_lineBuilder = (delimiter, item) => | ||
valueGetters.Select(getter => | ||
{ | ||
try | ||
{ | ||
return getter(item); | ||
} | ||
catch (Exception) | ||
{ | ||
return "error"; | ||
} | ||
}) | ||
.Aggregate((s1, s2) => $"{s1}{delimiter}{s2}"); | ||
} | ||
|
||
public static IEnumerable<string> Actual(IEnumerable<T> items, char delimiter) | ||
{ | ||
yield return _headerBuilder(delimiter); | ||
foreach (var item in items) | ||
{ | ||
yield return _lineBuilder(delimiter, item); | ||
} | ||
} | ||
|
||
private static Func<T, string> BuildPropertyGetter(PropertyInfo prop) | ||
{ | ||
// we're going to build an expression | ||
// if the property is a value type | ||
// item => item.Property.ToString() | ||
// otherwise | ||
// item => item.Property == null ? "null" : item.Property.ToString(); | ||
var type = typeof(T); | ||
var input = Expression.Parameter(typeof(T), "item"); | ||
|
||
var propExpression = Expression.Property(input, prop); | ||
|
||
var toString = prop.PropertyType != typeof(string) | ||
? (Expression)Expression.Call(propExpression, _toString) | ||
: propExpression; | ||
|
||
|
||
if (prop.PropertyType.IsValueType) | ||
{ | ||
var lambda = Expression.Lambda<Func<T, string>>(toString, input).Compile(); | ||
return item => EscapeAndWrap(lambda(item)); | ||
} | ||
else | ||
{ | ||
var nullExpression = Expression.Constant(null); | ||
var isNull = Expression.Equal(propExpression, nullExpression); | ||
var condition = Expression.Condition(isNull, Expression.Constant("null"), toString); | ||
|
||
var lambda = Expression.Lambda<Func<T, string>>(condition, input).Compile(); | ||
return item => EscapeAndWrap(lambda(item)); | ||
} | ||
} | ||
|
||
private static string EscapeAndWrap(string input) | ||
{ | ||
return input; | ||
return $"\"{input.Replace("\"", "\"\"")}\""; | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq.Expressions; | ||
using System.Reflection; | ||
using System.Text; | ||
|
||
namespace Samples.Reflection | ||
{ | ||
public class Examples | ||
{ | ||
|
||
void OkReflectionExamples<T>(MyType someObject) | ||
{ | ||
// getting a referene to a Type | ||
Type t = typeof(T); | ||
t = someObject.GetType(); | ||
|
||
// getting member information | ||
PropertyInfo propInfo = t.GetProperty( | ||
nameof(MyType.MyProperty), BindingFlags.Public | BindingFlags.Instance); | ||
|
||
MethodInfo methodInfo = t.GetMethod( | ||
nameof(MyType.MyMethod), BindingFlags.DeclaredOnly); | ||
|
||
//dicovering attributes | ||
var attribute = t.GetCustomAttribute<MyAttribute>(); | ||
|
||
//discovering if a type is derived from or implements another | ||
bool inherits = typeof(object).IsAssignableFrom(t); | ||
} | ||
|
||
void ExpensiveReflectionExamples( | ||
object someObject, | ||
PropertyInfo someProperty, | ||
MethodInfo someMethod) | ||
{ | ||
someProperty.GetValue(someObject); | ||
someMethod.Invoke(someObject, null); | ||
} | ||
|
||
public class MyAttribute : Attribute | ||
{ | ||
|
||
} | ||
|
||
public class MyType : MyInterface | ||
{ | ||
public int MyProperty { get; set; } | ||
public void MyMethod() { } | ||
} | ||
|
||
public interface MyInterface | ||
{ | ||
public int MyProperty { get; set; } | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.