// Copyright 2009-2022 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 Microsoft.CSharp.RuntimeBinder;
using System;
using System.Collections;
using System.Dynamic;
using System.Linq;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;
namespace CsvHelper.Expressions
{
///
/// Write dynamic records.
///
public class DynamicRecordWriter : RecordWriter
{
private readonly Hashtable getters = new Hashtable();
///
/// Initializes a new instance using the given writer.
///
/// The writer.
public DynamicRecordWriter(CsvWriter writer) : base(writer) { }
///
/// Creates a of type
/// that will write the given record using the current writer row.
///
/// The record type.
/// The record.
protected override Action CreateWriteDelegate(T record)
{
// http://stackoverflow.com/a/14011692/68499
Action action = r =>
{
var provider = (IDynamicMetaObjectProvider)r;
var type = provider.GetType();
var parameterExpression = Expression.Parameter(typeof(T), "record");
var metaObject = provider.GetMetaObject(parameterExpression);
var memberNames = metaObject.GetDynamicMemberNames();
if (Writer.Configuration.DynamicPropertySort != null)
{
memberNames = memberNames.OrderBy(name => name, Writer.Configuration.DynamicPropertySort);
}
foreach (var name in memberNames)
{
var value = GetValue(name, provider);
Writer.WriteField(value);
}
};
return action;
}
private object GetValue(string name, IDynamicMetaObjectProvider target)
{
// https://stackoverflow.com/a/30757547/68499
var callSite = (CallSite>)getters[name];
if (callSite == null)
{
var getMemberBinder = Binder.GetMember(CSharpBinderFlags.None, name, typeof(DynamicRecordWriter), new CSharpArgumentInfo[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) });
getters[name] = callSite = CallSite>.Create(getMemberBinder);
}
return callSite.Target(callSite, target);
}
}
}