-
Notifications
You must be signed in to change notification settings - Fork 775
Expand file tree
/
Copy pathDynamicObjectMemberAccessor.cs
More file actions
203 lines (172 loc) · 6.68 KB
/
DynamicObjectMemberAccessor.cs
File metadata and controls
203 lines (172 loc) · 6.68 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;
using Microsoft.CSharp.RuntimeBinder;
namespace Python.Runtime;
class DynamicObjectMemberAccessor
{
const int MaxCacheEntries = 1000;
readonly ConcurrentLruCache<MemberKey, Func<object, object>> getters = new(MaxCacheEntries);
readonly ConcurrentLruCache<MemberKey, Action<object, object?>> setters = new(MaxCacheEntries);
readonly ConcurrentLruCache<MemberKey, Func<object, bool>> deleters = new(MaxCacheEntries);
static readonly CSharpArgumentInfo[] getArgumentInfo =
{
CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
};
static readonly CSharpArgumentInfo[] setArgumentInfo =
{
CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
};
public bool TryGetMember(IDynamicMetaObjectProvider obj, string memberName, out object? value)
{
if (obj is null)
throw new ArgumentNullException(nameof(obj));
if (memberName is null)
throw new ArgumentNullException(nameof(memberName));
var getter = getters.GetOrAdd(new MemberKey(obj.GetType(), memberName), static key =>
{
if (typeof(DynamicObject).IsAssignableFrom(key.Type))
{
var getBinder = new GetMemberNameBinder(key.MemberName);
return obj =>
{
if (((DynamicObject)obj).TryGetMember(getBinder, out object? result))
{
return result;
}
throw new RuntimeBinderException($"Could not get member '{key.MemberName}'");
};
}
var binder = Binder.GetMember(CSharpBinderFlags.None, key.MemberName, key.Type, getArgumentInfo);
var callSite = CallSite<Func<CallSite, IDynamicMetaObjectProvider, object>>.Create(binder);
return obj => callSite.Target(callSite, (IDynamicMetaObjectProvider)obj);
});
try
{
value = getter(obj);
return true;
}
catch (RuntimeBinderException)
{
value = null;
return false;
}
}
public bool TrySetMember(IDynamicMetaObjectProvider obj, string memberName, object? value)
{
if (obj is null)
throw new ArgumentNullException(nameof(obj));
if (memberName is null)
throw new ArgumentNullException(nameof(memberName));
var setter = setters.GetOrAdd(new MemberKey(obj.GetType(), memberName), static key =>
{
if (typeof(DynamicObject).IsAssignableFrom(key.Type))
{
var setBinder = new SetMemberNameBinder(key.MemberName);
return (obj, value) =>
{
if (!((DynamicObject)obj).TrySetMember(setBinder, value))
{
throw new RuntimeBinderException($"Could not set member '{key.MemberName}'");
}
};
}
var binder = Binder.SetMember(CSharpBinderFlags.None, key.MemberName, key.Type, setArgumentInfo);
var callSite = CallSite<Action<CallSite, IDynamicMetaObjectProvider, object?>>.Create(binder);
return (obj, value) => callSite.Target(callSite, (IDynamicMetaObjectProvider)obj, value);
});
try
{
setter(obj, value);
return true;
}
catch (RuntimeBinderException)
{
return false;
}
}
public bool TryDeleteMember(IDynamicMetaObjectProvider obj, string memberName)
{
if (obj is null)
throw new ArgumentNullException(nameof(obj));
if (memberName is null)
throw new ArgumentNullException(nameof(memberName));
var deleter = deleters.GetOrAdd(new MemberKey(obj.GetType(), memberName), static key =>
{
if (typeof(DynamicObject).IsAssignableFrom(key.Type))
{
var binder = new DeleteMemberNameBinder(key.MemberName);
return obj => ((DynamicObject)obj).TryDeleteMember(binder);
}
if (typeof(ExpandoObject).IsAssignableFrom(key.Type))
{
return obj => ((IDictionary<string, object>)(ExpandoObject)obj).Remove(key.MemberName);
}
return _ => false;
});
try
{
return deleter(obj);
}
catch (RuntimeBinderException)
{
return false;
}
}
public IReadOnlyCollection<string> GetDynamicMemberNames(IDynamicMetaObjectProvider obj)
{
if (obj is null)
throw new ArgumentNullException(nameof(obj));
if (obj is ExpandoObject expandoObject)
{
return ((IDictionary<string, object>)expandoObject).Keys.ToArray();
}
if (obj is DynamicObject dynamicObject)
{
return dynamicObject.GetDynamicMemberNames().ToArray();
}
var metaObject = obj.GetMetaObject(Expression.Constant(obj));
return metaObject.GetDynamicMemberNames().ToArray();
}
readonly record struct MemberKey(Type Type, string MemberName);
sealed class DeleteMemberNameBinder : DeleteMemberBinder
{
public DeleteMemberNameBinder(string name)
: base(name, ignoreCase: false)
{
}
public override DynamicMetaObject FallbackDeleteMember(DynamicMetaObject target, DynamicMetaObject? errorSuggestion)
=> errorSuggestion ?? throw new RuntimeBinderException($"Could not delete member '{Name}'");
}
sealed class GetMemberNameBinder : GetMemberBinder
{
public GetMemberNameBinder(string name)
: base(name, ignoreCase: false)
{
}
public override DynamicMetaObject FallbackGetMember(DynamicMetaObject target, DynamicMetaObject? errorSuggestion)
=> errorSuggestion ?? throw new RuntimeBinderException($"Could not get member '{Name}'");
}
sealed class SetMemberNameBinder : SetMemberBinder
{
public SetMemberNameBinder(string name)
: base(name, ignoreCase: false)
{
}
public override DynamicMetaObject FallbackSetMember(
DynamicMetaObject target,
DynamicMetaObject value,
DynamicMetaObject? errorSuggestion)
=> errorSuggestion ?? throw new RuntimeBinderException($"Could not set member '{Name}'");
}
public void Clear()
{
getters.Clear();
setters.Clear();
deleters.Clear();
}
}