-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Created a fast dynamic object to use when reading.
Changed ExpandoObjectRecordWriter to be used with any IDictionary<string, object>.
- Loading branch information
Showing
6 changed files
with
248 additions
and
7 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
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,49 @@ | ||
// Copyright 2009-2024 Josh Close | ||
// This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. | ||
// See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. | ||
// https://github.com/JoshClose/CsvHelper | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
|
||
namespace CsvHelper.Expressions | ||
{ | ||
/// <summary> | ||
/// Writes expando objects. | ||
/// </summary> | ||
public class ExpandoObjectRecordWriter : RecordWriter | ||
{ | ||
/// <summary> | ||
/// Initializes a new instance using the given writer. | ||
/// </summary> | ||
/// <param name="writer">The writer.</param> | ||
public ExpandoObjectRecordWriter(CsvWriter writer) : base(writer) { } | ||
|
||
/// <summary> | ||
/// Creates a <see cref="Delegate"/> of type <see cref="Action{T}"/> | ||
/// that will write the given record using the current writer row. | ||
/// </summary> | ||
/// <typeparam name="T">The record type.</typeparam> | ||
/// <param name="recordType">The record.</param> | ||
protected override Action<T> CreateWriteDelegate<T>(Type recordType) | ||
{ | ||
Action<T> action = r => | ||
{ | ||
var dict = ((IDictionary<string, object>)r).AsEnumerable(); | ||
|
||
if (Writer.Configuration.DynamicPropertySort != null) | ||
{ | ||
dict = dict.OrderBy(pair => pair.Key, Writer.Configuration.DynamicPropertySort); | ||
} | ||
|
||
var values = dict.Select(pair => pair.Value); | ||
foreach (var val in values) | ||
{ | ||
Writer.WriteField(val); | ||
} | ||
}; | ||
|
||
return action; | ||
} | ||
} | ||
} |
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
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,165 @@ | ||
using System; | ||
using System.Collections; | ||
using System.Collections.Generic; | ||
using System.Dynamic; | ||
using System.Linq.Expressions; | ||
using System.Reflection; | ||
|
||
namespace CsvHelper; | ||
|
||
internal class FastDynamicObject : IDictionary<string, object>, IDynamicMetaObjectProvider | ||
{ | ||
private readonly Dictionary<string, object> dict; | ||
|
||
public FastDynamicObject() | ||
{ | ||
dict = new Dictionary<string, object>(); | ||
} | ||
|
||
object IDictionary<string, object>.this[string key] | ||
{ | ||
get | ||
{ | ||
if (!dict.ContainsKey(key)) | ||
{ | ||
throw new CsvHelperException($"{nameof(FastDynamicObject)} does not contain a definition for '{key}'."); | ||
} | ||
|
||
return dict[key]; | ||
} | ||
|
||
set | ||
{ | ||
dict[key] = value; | ||
} | ||
} | ||
|
||
object SetValue(string name, object value) | ||
{ | ||
dict[name] = value; | ||
|
||
return value; | ||
} | ||
|
||
ICollection<string> IDictionary<string, object>.Keys => throw new NotSupportedException(); | ||
|
||
ICollection<object> IDictionary<string, object>.Values => throw new NotSupportedException(); | ||
|
||
int ICollection<KeyValuePair<string, object>>.Count => throw new NotSupportedException(); | ||
|
||
bool ICollection<KeyValuePair<string, object>>.IsReadOnly => throw new NotSupportedException(); | ||
|
||
DynamicMetaObject IDynamicMetaObjectProvider.GetMetaObject(Expression parameter) | ||
{ | ||
return new FastDynamicMetaObject(parameter, BindingRestrictions.Empty, this); | ||
} | ||
|
||
void IDictionary<string, object>.Add(string key, object value) | ||
{ | ||
throw new NotSupportedException(); | ||
} | ||
|
||
void ICollection<KeyValuePair<string, object>>.Add(KeyValuePair<string, object> item) | ||
{ | ||
throw new NotSupportedException(); | ||
} | ||
|
||
void ICollection<KeyValuePair<string, object>>.Clear() | ||
{ | ||
throw new NotSupportedException(); | ||
} | ||
|
||
bool ICollection<KeyValuePair<string, object>>.Contains(KeyValuePair<string, object> item) | ||
{ | ||
throw new NotSupportedException(); | ||
} | ||
|
||
bool IDictionary<string, object>.ContainsKey(string key) | ||
{ | ||
throw new NotSupportedException(); | ||
} | ||
|
||
void ICollection<KeyValuePair<string, object>>.CopyTo(KeyValuePair<string, object>[] array, int arrayIndex) | ||
{ | ||
throw new NotSupportedException(); | ||
} | ||
|
||
IEnumerator<KeyValuePair<string, object>> IEnumerable<KeyValuePair<string, object>>.GetEnumerator() | ||
{ | ||
throw new NotSupportedException(); | ||
} | ||
|
||
IEnumerator IEnumerable.GetEnumerator() | ||
{ | ||
throw new NotSupportedException(); | ||
} | ||
|
||
bool IDictionary<string, object>.Remove(string key) | ||
{ | ||
throw new NotSupportedException(); | ||
} | ||
|
||
bool ICollection<KeyValuePair<string, object>>.Remove(KeyValuePair<string, object> item) | ||
{ | ||
throw new NotSupportedException(); | ||
} | ||
|
||
bool IDictionary<string, object>.TryGetValue(string key, out object value) | ||
{ | ||
throw new NotSupportedException(); | ||
} | ||
|
||
private class FastDynamicMetaObject : DynamicMetaObject | ||
{ | ||
private static readonly MethodInfo getValueMethod = typeof(IDictionary<string, object>).GetProperty("Item")!.GetGetMethod()!; | ||
private static readonly MethodInfo setValueMethod = typeof(FastDynamicObject).GetMethod("SetValue", BindingFlags.NonPublic | BindingFlags.Instance)!; | ||
|
||
public FastDynamicMetaObject(Expression expression, BindingRestrictions restrictions) : base(expression, restrictions) { } | ||
|
||
public FastDynamicMetaObject(Expression expression, BindingRestrictions restrictions, object value) : base(expression, restrictions, value) { } | ||
|
||
public override DynamicMetaObject BindGetMember(GetMemberBinder binder) | ||
{ | ||
var parameters = new Expression[] { Expression.Constant(binder.Name) }; | ||
|
||
var callMethod = CallMethod(getValueMethod, parameters); | ||
|
||
return callMethod; | ||
} | ||
|
||
public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicMetaObject value) | ||
{ | ||
var parameters = new Expression[] { Expression.Constant(binder.Name), Expression.Convert(value.Expression, typeof(object)) }; | ||
|
||
var callMethod = CallMethod(setValueMethod, parameters); | ||
|
||
return callMethod; | ||
} | ||
|
||
public override DynamicMetaObject BindInvokeMember(InvokeMemberBinder binder, DynamicMetaObject[] args) | ||
{ | ||
var parameters = new Expression[] { Expression.Constant(binder.Name) }; | ||
|
||
var callMethod = CallMethod(getValueMethod, parameters); | ||
|
||
return callMethod; | ||
} | ||
|
||
public override IEnumerable<string> GetDynamicMemberNames() | ||
{ | ||
if (HasValue && Value is IDictionary<string, object> lookup) | ||
{ | ||
return lookup.Keys; | ||
} | ||
|
||
return Array.Empty<string>(); | ||
} | ||
|
||
private DynamicMetaObject CallMethod(MethodInfo method, Expression[] parameters) | ||
{ | ||
var callMethod = new DynamicMetaObject(Expression.Call(Expression.Convert(Expression, LimitType), method, parameters), BindingRestrictions.GetTypeRestriction(Expression, LimitType)); | ||
|
||
return callMethod; | ||
} | ||
} | ||
} |
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,21 @@ | ||
using Xunit; | ||
|
||
namespace CsvHelper.Tests.Dynamic | ||
{ | ||
public class CsvRowDynamicObjectTests | ||
{ | ||
[Fact] | ||
public void Dynamic_SetAndGet_Works() | ||
{ | ||
dynamic obj = new FastDynamicObject(); | ||
obj.Id = 1; | ||
obj.Name = "one"; | ||
|
||
var id = obj.Id; | ||
var name = obj.Name; | ||
|
||
Assert.Equal(1, id); | ||
Assert.Equal("one", name); | ||
} | ||
} | ||
} |