diff options
author | Jon Skeet <jonskeet@google.com> | 2015-07-01 14:47:03 +0100 |
---|---|---|
committer | Jon Skeet <jonskeet@google.com> | 2015-07-09 08:24:49 +0100 |
commit | 78ea98f56f7a0a028e378aee6394549707e199bc (patch) | |
tree | b3275e8d8b01528dc58af6ec0877c4ee79cd1b54 /csharp/src/ProtocolBuffers/FieldAccess | |
parent | 3805b43009d35422bed85c56aaf2d6ce4d007fe5 (diff) | |
download | protobuf-78ea98f56f7a0a028e378aee6394549707e199bc.tar.gz protobuf-78ea98f56f7a0a028e378aee6394549707e199bc.tar.bz2 protobuf-78ea98f56f7a0a028e378aee6394549707e199bc.zip |
Implement reflection properly for fields.
- FieldAccessorTable is now non-generic
- We don't have a static field per message type in the umbrella class. (Message descriptors are accessed via the file descriptor.)
- Removed the "descriptor assigner" complication from the descriptor fixup; without extensions, we don't need it
- MapField implements IDictionary (more tests would be good...)
- RepeatedField implements IList (more tests would be good)
- Use expression trees to build accessors. (Will need to test this on various platforms... probably need a fallback strategy just using reflection directly.)
- Added FieldDescriptor.IsMap
- Added tests for reflection with generated messages
Changes to generated code coming in next commit.
Diffstat (limited to 'csharp/src/ProtocolBuffers/FieldAccess')
7 files changed, 159 insertions, 178 deletions
diff --git a/csharp/src/ProtocolBuffers/FieldAccess/FieldAccessorBase.cs b/csharp/src/ProtocolBuffers/FieldAccess/FieldAccessorBase.cs index 73d777b2..2a3e5b8b 100644 --- a/csharp/src/ProtocolBuffers/FieldAccess/FieldAccessorBase.cs +++ b/csharp/src/ProtocolBuffers/FieldAccess/FieldAccessorBase.cs @@ -32,34 +32,37 @@ using System; using System.Reflection; +using Google.Protobuf.Descriptors; namespace Google.Protobuf.FieldAccess { /// <summary> /// Base class for field accessors. /// </summary> - /// <typeparam name="T">Type of message containing the field</typeparam> - internal abstract class FieldAccessorBase<T> : IFieldAccessor<T> where T : IMessage<T> + internal abstract class FieldAccessorBase : IFieldAccessor { - private readonly Func<T, object> getValueDelegate; + private readonly Func<object, object> getValueDelegate; + private readonly FieldDescriptor descriptor; - internal FieldAccessorBase(string name) + internal FieldAccessorBase(Type type, string propertyName, FieldDescriptor descriptor) { - PropertyInfo property = typeof(T).GetProperty(name); + PropertyInfo property = type.GetProperty(propertyName); if (property == null || !property.CanRead) { throw new ArgumentException("Not all required properties/methods available"); } - getValueDelegate = ReflectionUtil.CreateUpcastDelegate<T>(property.GetGetMethod()); + this.descriptor = descriptor; + getValueDelegate = ReflectionUtil.CreateFuncObjectObject(property.GetGetMethod()); } - public object GetValue(T message) + public FieldDescriptor Descriptor { get { return descriptor; } } + + public object GetValue(object message) { return getValueDelegate(message); } - public abstract bool HasValue(T message); - public abstract void Clear(T message); - public abstract void SetValue(T message, object value); + public abstract void Clear(object message); + public abstract void SetValue(object message, object value); } } diff --git a/csharp/src/ProtocolBuffers/FieldAccess/FieldAccessorTable.cs b/csharp/src/ProtocolBuffers/FieldAccess/FieldAccessorTable.cs index 6379ff25..57ea9c87 100644 --- a/csharp/src/ProtocolBuffers/FieldAccess/FieldAccessorTable.cs +++ b/csharp/src/ProtocolBuffers/FieldAccess/FieldAccessorTable.cs @@ -31,6 +31,7 @@ #endregion
using System;
+using System.Collections.ObjectModel;
using Google.Protobuf.Descriptors;
namespace Google.Protobuf.FieldAccess
@@ -38,34 +39,43 @@ namespace Google.Protobuf.FieldAccess /// <summary>
/// Provides access to fields in generated messages via reflection.
/// </summary>
- public sealed class FieldAccessorTable<T> where T : IMessage<T>
+ public sealed class FieldAccessorTable
{
- private readonly IFieldAccessor<T>[] accessors;
+ private readonly ReadOnlyCollection<IFieldAccessor> accessors;
private readonly MessageDescriptor descriptor;
/// <summary>
/// Constructs a FieldAccessorTable for a particular message class.
/// Only one FieldAccessorTable should be constructed per class.
/// </summary>
+ /// <param name="type">The CLR type for the message.</param>
/// <param name="descriptor">The type's descriptor</param>
/// <param name="propertyNames">The Pascal-case names of all the field-based properties in the message.</param>
- public FieldAccessorTable(MessageDescriptor descriptor, string[] propertyNames)
+ public FieldAccessorTable(Type type, MessageDescriptor descriptor, string[] propertyNames)
{
this.descriptor = descriptor;
- accessors = new IFieldAccessor<T>[descriptor.Fields.Count];
- bool supportFieldPresence = descriptor.File.Syntax == FileDescriptor.ProtoSyntax.Proto2;
- for (int i = 0; i < accessors.Length; i++)
+ var accessorsArray = new IFieldAccessor[descriptor.Fields.Count];
+ for (int i = 0; i < accessorsArray.Length; i++)
{
var field = descriptor.Fields[i];
var name = propertyNames[i];
- accessors[i] = field.IsRepeated
- ? (IFieldAccessor<T>) new RepeatedFieldAccessor<T>(propertyNames[i])
- : new SingleFieldAccessor<T>(field, name, supportFieldPresence);
+ accessorsArray[i] =
+ field.IsMap ? new MapFieldAccessor(type, name, field)
+ : field.IsRepeated ? new RepeatedFieldAccessor(type, name, field)
+ : (IFieldAccessor) new SingleFieldAccessor(type, name, field);
}
+ accessors = new ReadOnlyCollection<IFieldAccessor>(accessorsArray);
// TODO(jonskeet): Oneof support
}
- internal IFieldAccessor<T> this[int fieldNumber]
+ // TODO: Validate the name here... should possibly make this type a more "general reflection access" type,
+ // bearing in mind the oneof parts to come as well.
+ /// <summary>
+ /// Returns all of the field accessors for the message type.
+ /// </summary>
+ public ReadOnlyCollection<IFieldAccessor> Accessors { get { return accessors; } }
+
+ public IFieldAccessor this[int fieldNumber]
{
get
{
@@ -74,7 +84,7 @@ namespace Google.Protobuf.FieldAccess }
}
- internal IFieldAccessor<T> this[FieldDescriptor field]
+ internal IFieldAccessor this[FieldDescriptor field]
{
get
{
diff --git a/csharp/src/ProtocolBuffers/FieldAccess/IFieldAccessor.cs b/csharp/src/ProtocolBuffers/FieldAccess/IFieldAccessor.cs index 61838543..77e7146d 100644 --- a/csharp/src/ProtocolBuffers/FieldAccess/IFieldAccessor.cs +++ b/csharp/src/ProtocolBuffers/FieldAccess/IFieldAccessor.cs @@ -30,39 +30,41 @@ // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
+using Google.Protobuf.Descriptors;
+
namespace Google.Protobuf.FieldAccess
{
/// <summary>
- /// Allows fields to be reflectively accessed in a smart manner.
- /// The property descriptors for each field are created once and then cached.
- /// In addition, this interface holds knowledge of repeated fields, builders etc.
+ /// Allows fields to be reflectively accessed.
/// </summary>
- internal interface IFieldAccessor<T> where T : IMessage<T>
+ public interface IFieldAccessor
{
/// <summary>
- /// Indicates whether the specified message contains the field. For primitive fields
- /// declared in proto3-syntax messages, this simply checks whether the value is the default one.
+ /// Returns the descriptor associated with this field.
/// </summary>
- /// <exception cref="InvalidOperationException">The field is a repeated field, or a single primitive field.</exception>
- bool HasValue(T message);
+ FieldDescriptor Descriptor { get; }
/// <summary>
/// Clears the field in the specified message. (For repeated fields,
/// this clears the list.)
/// </summary>
- void Clear(T message);
+ void Clear(object message);
/// <summary>
/// Fetches the field value. For repeated values, this will be an
- /// <see cref="IList"/> implementation.
+ /// <see cref="IList"/> implementation. For map values, this will be an
+ /// <see cref="IDictionary"/> implementation.
/// </summary>
- object GetValue(T message);
+ object GetValue(object message);
/// <summary>
- /// Mutator for single fields only. (Repeated fields must be mutated
- /// by fetching the list, then mutating that.)
+ /// Mutator for single "simple" fields only.
/// </summary>
- /// <exception cref="InvalidOperationException">The field is a repeated field.</exception>
- void SetValue(T message, object value);
+ /// <remarks>
+ /// Repeated fields are mutated by fetching the value and manipulating it as a list.
+ /// Map fields are mutated by fetching the value and manipulating it as a dictionary.
+ /// </remarks>
+ /// <exception cref="InvalidOperationException">The field is not a "simple" field, or the message is frozen.</exception>
+ void SetValue(object message, object value);
}
}
\ No newline at end of file diff --git a/csharp/src/ProtocolBuffers/FieldAccess/MapFieldAccessor.cs b/csharp/src/ProtocolBuffers/FieldAccess/MapFieldAccessor.cs new file mode 100644 index 00000000..100dbb37 --- /dev/null +++ b/csharp/src/ProtocolBuffers/FieldAccess/MapFieldAccessor.cs @@ -0,0 +1,59 @@ +#region Copyright notice and license +// Protocol Buffers - Google's data interchange format +// Copyright 2015 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#endregion + +using System; +using System.Collections; +using Google.Protobuf.Descriptors; + +namespace Google.Protobuf.FieldAccess +{ + /// <summary> + /// Accessor for map fields. + /// </summary> + internal sealed class MapFieldAccessor : FieldAccessorBase + { + internal MapFieldAccessor(Type type, string propertyName, FieldDescriptor descriptor) : base(type, propertyName, descriptor) + { + } + + public override void Clear(object message) + { + IDictionary list = (IDictionary) GetValue(message); + list.Clear(); + } + + public override void SetValue(object message, object value) + { + throw new InvalidOperationException("SetValue is not implemented for map fields"); + } + } +} diff --git a/csharp/src/ProtocolBuffers/FieldAccess/ReflectionUtil.cs b/csharp/src/ProtocolBuffers/FieldAccess/ReflectionUtil.cs index 29399b0c..d3053920 100644 --- a/csharp/src/ProtocolBuffers/FieldAccess/ReflectionUtil.cs +++ b/csharp/src/ProtocolBuffers/FieldAccess/ReflectionUtil.cs @@ -31,6 +31,7 @@ #endregion
using System;
+using System.Linq.Expressions;
using System.Reflection;
namespace Google.Protobuf.FieldAccess
@@ -51,101 +52,42 @@ namespace Google.Protobuf.FieldAccess internal static readonly Type[] EmptyTypes = new Type[0];
/// <summary>
- /// Creates a delegate which will execute the given method and then return
- /// the result as an object.
+ /// Creates a delegate which will cast the argument to the appropriate method target type,
+ /// call the method on it, then convert the result to object.
/// </summary>
- public static Func<T, object> CreateUpcastDelegate<T>(MethodInfo method)
+ internal static Func<object, object> CreateFuncObjectObject(MethodInfo method)
{
- // The tricky bit is invoking CreateCreateUpcastDelegateImpl with the right type parameters
- MethodInfo openImpl = typeof(ReflectionUtil).GetMethod("CreateUpcastDelegateImpl");
- MethodInfo closedImpl = openImpl.MakeGenericMethod(typeof(T), method.ReturnType);
- return (Func<T, object>) closedImpl.Invoke(null, new object[] {method});
+ ParameterExpression parameter = Expression.Parameter(typeof(object), "p");
+ Expression downcast = Expression.Convert(parameter, method.DeclaringType);
+ Expression call = Expression.Call(downcast, method);
+ Expression upcast = Expression.Convert(call, typeof(object));
+ return Expression.Lambda<Func<object, object>>(upcast, parameter).Compile();
}
-
+
/// <summary>
- /// Method used solely for implementing CreateUpcastDelegate. Public to avoid trust issues
- /// in low-trust scenarios.
+ /// Creates a delegate which will execute the given method after casting the first argument to
+ /// the target type of the method, and the second argument to the first parameter type of the method.
/// </summary>
- public static Func<TSource, object> CreateUpcastDelegateImpl<TSource, TResult>(MethodInfo method)
+ internal static Action<object, object> CreateActionObjectObject(MethodInfo method)
{
- // Convert the reflection call into an open delegate, i.e. instead of calling x.Method()
- // we'll call getter(x).
- Func<TSource, TResult> getter = ReflectionUtil.CreateDelegateFunc<TSource, TResult>(method);
-
- // Implicit upcast to object (within the delegate)
- return source => getter(source);
+ ParameterExpression targetParameter = Expression.Parameter(typeof(object), "target");
+ ParameterExpression argParameter = Expression.Parameter(typeof(object), "arg");
+ Expression castTarget = Expression.Convert(targetParameter, method.DeclaringType);
+ Expression castArgument = Expression.Convert(argParameter, method.GetParameters()[0].ParameterType);
+ Expression call = Expression.Call(castTarget, method, castArgument);
+ return Expression.Lambda<Action<object, object>>(call, targetParameter, argParameter).Compile();
}
/// <summary>
- /// Creates a delegate which will execute the given method after casting the parameter
- /// down from object to the required parameter type.
+ /// Creates a delegate which will execute the given method after casting the first argument to
+ /// the target type of the method.
/// </summary>
- public static Action<T, object> CreateDowncastDelegate<T>(MethodInfo method)
- {
- MethodInfo openImpl = typeof(ReflectionUtil).GetMethod("CreateDowncastDelegateImpl");
- MethodInfo closedImpl = openImpl.MakeGenericMethod(typeof(T), method.GetParameters()[0].ParameterType);
- return (Action<T, object>) closedImpl.Invoke(null, new object[] {method});
- }
-
- public static Action<TSource, object> CreateDowncastDelegateImpl<TSource, TParam>(MethodInfo method)
- {
- // Convert the reflection call into an open delegate, i.e. instead of calling x.Method(y) we'll
- // call Method(x, y)
- Action<TSource, TParam> call = ReflectionUtil.CreateDelegateAction<TSource, TParam>(method);
-
- return (source, parameter) => call(source, (TParam) parameter);
- }
-
- /// <summary>
- /// Creates a delegate which will execute the given method after casting the parameter
- /// down from object to the required parameter type.
- /// </summary>
- public static Action<T, object> CreateDowncastDelegateIgnoringReturn<T>(MethodInfo method)
- {
- MethodInfo openImpl = typeof(ReflectionUtil).GetMethod("CreateDowncastDelegateIgnoringReturnImpl");
- MethodInfo closedImpl = openImpl.MakeGenericMethod(typeof(T), method.GetParameters()[0].ParameterType,
- method.ReturnType);
- return (Action<T, object>) closedImpl.Invoke(null, new object[] {method});
- }
-
- public static Action<TSource, object> CreateDowncastDelegateIgnoringReturnImpl<TSource, TParam, TReturn>(
- MethodInfo method)
- {
- // Convert the reflection call into an open delegate, i.e. instead of calling x.Method(y) we'll
- // call Method(x, y)
- Func<TSource, TParam, TReturn> call = ReflectionUtil.CreateDelegateFunc<TSource, TParam, TReturn>(method);
-
- return delegate(TSource source, object parameter) { call(source, (TParam) parameter); };
- }
-
- internal static Func<TResult> CreateDelegateFunc<TResult>(MethodInfo method)
- {
- object tdelegate = Delegate.CreateDelegate(typeof(Func<TResult>), null, method);
- return (Func<TResult>)tdelegate;
- }
-
- internal static Func<T, TResult> CreateDelegateFunc<T, TResult>(MethodInfo method)
- {
- object tdelegate = Delegate.CreateDelegate(typeof(Func<T, TResult>), null, method);
- return (Func<T, TResult>)tdelegate;
- }
-
- internal static Func<T1, T2, TResult> CreateDelegateFunc<T1, T2, TResult>(MethodInfo method)
- {
- object tdelegate = Delegate.CreateDelegate(typeof(Func<T1, T2, TResult>), null, method);
- return (Func<T1, T2, TResult>)tdelegate;
- }
-
- internal static Action<T> CreateDelegateAction<T>(MethodInfo method)
- {
- object tdelegate = Delegate.CreateDelegate(typeof(Action<T>), null, method);
- return (Action<T>)tdelegate;
- }
-
- internal static Action<T1, T2> CreateDelegateAction<T1, T2>(MethodInfo method)
+ internal static Action<object> CreateActionObject(MethodInfo method)
{
- object tdelegate = Delegate.CreateDelegate(typeof(Action<T1, T2>), null, method);
- return (Action<T1, T2>)tdelegate;
+ ParameterExpression targetParameter = Expression.Parameter(typeof(object), "target");
+ Expression castTarget = Expression.Convert(targetParameter, method.DeclaringType);
+ Expression call = Expression.Call(castTarget, method);
+ return Expression.Lambda<Action<object>>(call, targetParameter).Compile();
}
}
}
\ No newline at end of file diff --git a/csharp/src/ProtocolBuffers/FieldAccess/RepeatedFieldAccessor.cs b/csharp/src/ProtocolBuffers/FieldAccess/RepeatedFieldAccessor.cs index b12278f9..8d7ecbaf 100644 --- a/csharp/src/ProtocolBuffers/FieldAccess/RepeatedFieldAccessor.cs +++ b/csharp/src/ProtocolBuffers/FieldAccess/RepeatedFieldAccessor.cs @@ -32,33 +32,28 @@ using System; using System.Collections; +using Google.Protobuf.Descriptors; namespace Google.Protobuf.FieldAccess { /// <summary> /// Accessor for repeated fields. /// </summary> - /// <typeparam name="T">The type of message containing the field.</typeparam> - internal sealed class RepeatedFieldAccessor<T> : FieldAccessorBase<T> where T : IMessage<T> + internal sealed class RepeatedFieldAccessor : FieldAccessorBase { - internal RepeatedFieldAccessor(string name) : base(name) + internal RepeatedFieldAccessor(Type type, string propertyName, FieldDescriptor descriptor) : base(type, propertyName, descriptor) { } - public override void Clear(T message) + public override void Clear(object message) { IList list = (IList) GetValue(message); list.Clear(); } - public override bool HasValue(T message) + public override void SetValue(object message, object value) { - throw new NotImplementedException("HasValue is not implemented for repeated fields"); - } - - public override void SetValue(T message, object value) - { - throw new NotImplementedException("SetValue is not implemented for repeated fields"); + throw new InvalidOperationException("SetValue is not implemented for repeated fields"); } } diff --git a/csharp/src/ProtocolBuffers/FieldAccess/SingleFieldAccessor.cs b/csharp/src/ProtocolBuffers/FieldAccess/SingleFieldAccessor.cs index 7a8f726e..cdc89e8d 100644 --- a/csharp/src/ProtocolBuffers/FieldAccess/SingleFieldAccessor.cs +++ b/csharp/src/ProtocolBuffers/FieldAccess/SingleFieldAccessor.cs @@ -39,76 +39,46 @@ namespace Google.Protobuf.FieldAccess /// <summary> /// Accessor for single fields. /// </summary> - /// <typeparam name="T">The type of message containing the field.</typeparam> - internal sealed class SingleFieldAccessor<T> : FieldAccessorBase<T> where T : IMessage<T> + internal sealed class SingleFieldAccessor : FieldAccessorBase { // All the work here is actually done in the constructor - it creates the appropriate delegates. // There are various cases to consider, based on the property type (message, string/bytes, or "genuine" primitive) // and proto2 vs proto3 for non-message types, as proto3 doesn't support "full" presence detection or default // values. - private readonly Action<T, object> setValueDelegate; - private readonly Action<T> clearDelegate; - private readonly Func<T, bool> hasValueDelegate; + private readonly Action<object, object> setValueDelegate; + private readonly Action<object> clearDelegate; - internal SingleFieldAccessor(FieldDescriptor descriptor, string name, bool supportsFieldPresence) : base(name) + internal SingleFieldAccessor(Type type, string propertyName, FieldDescriptor descriptor) : base(type, propertyName, descriptor) { - PropertyInfo property = typeof(T).GetProperty(name); + PropertyInfo property = type.GetProperty(propertyName); // We know there *is* such a property, or the base class constructor would have thrown. We should be able to write // to it though. if (!property.CanWrite) { throw new ArgumentException("Not all required properties/methods available"); } - setValueDelegate = ReflectionUtil.CreateDowncastDelegate<T>(property.GetSetMethod()); + setValueDelegate = ReflectionUtil.CreateActionObjectObject(property.GetSetMethod()); var clrType = property.PropertyType; + + // TODO: What should clear on a oneof member do? Clear the oneof? - if (typeof(IMessage).IsAssignableFrom(clrType)) - { - // Message types are simple - we only need to detect nullity. - clearDelegate = message => SetValue(message, null); - hasValueDelegate = message => GetValue(message) == null; - } - - if (supportsFieldPresence) - { - // Proto2: we expect a HasFoo property and a ClearFoo method. - // For strings and byte arrays, setting the property to null would have the equivalent effect, - // but we generate the method for consistency, which makes this simpler. - PropertyInfo hasProperty = typeof(T).GetProperty("Has" + name); - MethodInfo clearMethod = typeof(T).GetMethod("Clear" + name); - if (hasProperty == null || clearMethod == null || !hasProperty.CanRead) - { - throw new ArgumentException("Not all required properties/methods available"); - } - hasValueDelegate = ReflectionUtil.CreateDelegateFunc<T, bool>(hasProperty.GetGetMethod()); - clearDelegate = ReflectionUtil.CreateDelegateAction<T>(clearMethod); - } - else - { - /* - // TODO(jonskeet): Reimplement. We need a better way of working out default values. - // Proto3: for field detection, we just need the default value of the field (0, "", byte[0] etc) - // To clear a field, we set the value to that default. - object defaultValue = descriptor.DefaultValue; - hasValueDelegate = message => GetValue(message).Equals(defaultValue); - clearDelegate = message => SetValue(message, defaultValue); - */ - } - } - - public override bool HasValue(T message) - { - return hasValueDelegate(message); + // TODO: Validate that this is a reasonable single field? (Should be a value type, a message type, or string/ByteString.) + object defaultValue = + typeof(IMessage).IsAssignableFrom(clrType) ? null + : clrType == typeof(string) ? "" + : clrType == typeof(ByteString) ? ByteString.Empty + : Activator.CreateInstance(clrType); + clearDelegate = message => SetValue(message, defaultValue); } - public override void Clear(T message) + public override void Clear(object message) { clearDelegate(message); } - public override void SetValue(T message, object value) + public override void SetValue(object message, object value) { setValueDelegate(message, value); } |