I want to store callbacks for WebSocket calls from the clientside.
I want to tell the IDE the first param should always be ClientMetadata.
The problem is, that I want to register methods which would have infinite amount and unknown type of parameters but the first must be a ClientMetadata object.
I want to register the callbacks like this:
RegisterEventHandler("callback1", (ClientMetadata client, string smth1, int smth2);
RegisterEventHandler("callback2", (ClientMetadata client, bool smth3);
I have a method to store callbacks:
public static void RegisterEventHandler(string identifier, Delegate callback)
{
callbacks.Add(identifier, callback);
}
Here is where the callbacks are stored:
private static Dictionary<string, EventHandlerCallback> callbacks = new Dictionary<string, EventHandlerCallback>();
Here is how the stored methods get invoked:
private static void Server_MessageReceive2d(object? sender, MessageReceivedEventArgs e)
{
string jsonObj = Encoding.UTF8.GetString(e.Data);
if (jsonObj != null)
{
WSMessage obj = JsonConvert.DeserializeObject<WSMessage>(jsonObj);
if (obj == null || obj.message == null)
{
return;
}
//obj.identifier is the id of the callback in the serverside
if (callbacks.ContainsKey(obj.identifier))
{
MethodInfo mi = callbacks[obj.identifier].GetMethodInfo();
if (obj.callbackId != null && mi.ReturnType.ToString() != "System.Void")
{
//e.Client --> ClientMetaData
//obj.message --> dynamic[]?
var ret = callbacks[obj.identifier].DynamicInvoke(e.Client, obj.message);
//obj.callbackId is the callback to call when the method ended
if (ret != null)
{
SendMessage(e.Client, obj.callbackId, ret);
}
else
{
SendMessage(e.Client, obj.callbackId);
}
}
else
{
callbacks[obj.identifier].DynamicInvoke(e.Client, obj.message);
}
}
else
{
return;
}
}
}
I tried making a custom delegate:
delegate void EventHandlerCallback(ClientMetadata clientMetadata, params object[] data);
Turns out, the params keyword does absolutely nothing in this situation. Either ways I use params or not, my IDE expects this:
RegisterEventHandler("callback2", (ClientMetadata client, object[] smthObj);
You can create a delegate that takes a ClientMetadata object as the first argument and an array of objects as the second argument. This way, you can store callbacks with any number of unknown parameters:
delegate void EventHandlerCallback(ClientMetadata clientMetadata, object[] data);
public static void RegisterEventHandler(string identifier,
EventHandlerCallback callback)
{
callbacks.Add(identifier, callback);
}
// Usage:
RegisterEventHandler("callback1", (ClientMetadata client, object[] args) =>
{
string smth1 = (string)args[0];
int smth2 = (int)args[1];
});
RegisterEventHandler("callback2", (ClientMetadata client, object[] args) =>
{
bool smth3 = (bool)args[0];
});
You can go for Code Analyzer... Something like this should do the trick:
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using System.Collections.Immutable;
namespace AnalyzerTest
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class AnalyzerTestAnalyzer : DiagnosticAnalyzer
{
public const string ExpectedParameterType = "ClientMetadata";
public const string MethodName = "RegisterEventHandler";
public const string DiagnosticId = "SEL001";
public const string Title = "AnalyzerTest";
public const string MessageFormat = "Method call '{0}' wrong argument. First parameter of delegate should be '{1}'";
public const string Category = "Error";
private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Error, isEnabledByDefault: true);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get { return ImmutableArray.Create(Rule); } }
public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze);
context.EnableConcurrentExecution();
context.RegisterSyntaxNodeAction(NodeAction, new SyntaxKind[] { SyntaxKind.InvocationExpression });
}
private void NodeAction(SyntaxNodeAnalysisContext context)
{
var registerMethodSymbol = (IMethodSymbol)context.SemanticModel.GetSymbolInfo(context.Node).Symbol;
if(registerMethodSymbol.Name == MethodName)
{
IMethodSymbol methodSymbol;
var callbackSyntax = ((InvocationExpressionSyntax)context.Node).ArgumentList.Arguments[1];
var callbackTypeInfo = context.SemanticModel.GetTypeInfo(callbackSyntax.Expression);
if(callbackTypeInfo.Type != null)
{
var namedType = callbackTypeInfo.Type as INamedTypeSymbol;
//Action<ClientMetadata, ...> or Func<ClientMetadata, ..., ret> or delegate
methodSymbol = namedType.DelegateInvokeMethod;
}
else
{
//Method
var symbolInfo = context.SemanticModel.GetSymbolInfo(callbackSyntax.Expression);
methodSymbol = (symbolInfo.Symbol ?? (symbolInfo.CandidateSymbols != null ? symbolInfo.CandidateSymbols[0] : null)) as IMethodSymbol;
}
if(methodSymbol != null && methodSymbol.Parameters.Length > 0 && methodSymbol.Parameters[0].Type.Name == ExpectedParameterType)
{
return;
}
var diagnostic = Diagnostic.Create(Rule, callbackSyntax.GetLocation(), MethodName, ExpectedParameterType);
context.ReportDiagnostic(diagnostic);
}
}
}
}
Here is example how it works (of course it causing compile time error)
Related
I want to generate the right object with one code line and not a switch case because always when a new device is added I have to add a new line.
Is it possible to do that in one line without switch case?
public static Device GetDevice(Device.enumDevice TypeOfDevice, string alias)
{
// Create the Object with using reflection
switch (TypeOfDevice)
{
case Device.enumDevice.A34411:
return new A34411(string alias);
break;
case Device.enumDevice.N5744:
return new N5744(string alias);
break;
default:
throw new NotImplementedException();
}
return null;
}
You could store the factory methods as delegates in a dictionary
private static Dictionary<Device.enumDevice, Func<string, Device>> _factoryDict =
new Dictionary<Device.enumDevice, Func<string, Device>>{
[Device.enumDevice.A34411] = (alias) => new A34411(alias),
[Device.enumDevice.N5744] = (alias) => new N5744(alias),
};
...
public static Device GetDevice(Device.enumDevice TypeOfDevice, string alias)
{
if (_factoryDict.TryGetValue(TypeOfDevice, out var factory)) {
return factory(alias);
}
throw new NotImplementedException();
// No retun statement here, as it would be unreachable because of the throw statement.
}
Or, using reflection:
const string deviceNameSpace = "MyName.MyProject.Devices.";
public static Device GetDevice(Device.enumDevice deviceType, string alias)
{
string typeName = deviceNameSpace + deviceType.ToString();
Type type = Type.GetType(typeName, throwOnError: true);
return (Device)Activator.CreateInstance(type, alias);
}
An elegant approach would be to use Dependency Injection with "Named Type Registrations"
Fast, but not quite a complete example:
public abstract class Device
{
protected Device(string alias)
{
Alias = alias;
}
public string Alias { get; }
}
public class A1 : Device
{
public A1(string alias) : base(alias) { }
}
public class A2 : Device
{
public A2(string alias) : base(alias) { }
}
class DeviceAttribute : Attribute
{
public DeviceAttribute(Type type)
{
Type = type;
}
public Type Type { get; }
}
public enum DeviceEnum
{
[Device(typeof(A1))]
A1,
[Device(typeof(A2))]
A2
}
public static class DeviceEnumExtension
{
public static Device GetInstance(this DeviceEnum obj, string alias)
{
var member = typeof(DeviceEnum).GetMember(obj.ToString());
if (member[0].GetCustomAttributes(typeof(DeviceAttribute), false)[0] is DeviceAttribute deviceAttr)
{
var ctor = deviceAttr.Type.GetConstructor(new[] {typeof(string)});
return ctor.Invoke(new object[] {alias}) as Device;
}
return null;
}
}
public class UnitTest1
{
[Fact]
public void Test1()
{
// Arrange
var a1 = DeviceEnum.A1;
var a2 = DeviceEnum.A2;
// Act
var instanceA1 = a1.GetInstance("A1");
var instanceA2 = a2.GetInstance("A2");
// Assert
Assert.Equal(typeof(A1), instanceA1.GetType());
Assert.Equal(typeof(A2), instanceA2.GetType());
Assert.Equal("A1", instanceA1.Alias);
Assert.Equal("A2", instanceA2.Alias);
}
}
I have a simple invoker where, in order to be able to use a cache library , I need to know the name of the invoked method of an object that is a parameter of a Func delegate.
class Program
{
static void Main(string[] args)
{
var proxy = new Proxy();
Invoker.invoke(proxy, p => p.formatSomething("Dumb test"));
}
}
public class Proxy
{
public string formatSomething(string input){
return String.Format("-===={0}====-", input);
}
}
public static class Invoker
{
public static void invoke(Proxy proxy, Func<Proxy,string> online){
//Some caching logic that require the name of the method
//invoked on the proxy (in this specific case "formatSomething")
var methodName = ??;
if (IsCached(proxyName, methodName)){
output = GetFromCache(proxyName, methodName);
}else{
output = online(proxy);
}
}
}
These are some possible (bad) solutions:
Solution 1: Add a string parameter passing the method name (error prone)
public static class Invoker
{
public static void invoke(Proxy proxy, Func<Proxy,string> online, string methodName){
if (IsCached(proxyName, methodName)){
output = GetFromCache(proxyName, methodName);
}else{
output = online(proxy);
}
}
}
Solution 2: using Expression with possible performance issues.
public static class Invoker
{
public static void invoke(Proxy proxy, Expression<Func<Proxy,string>> online){
var methodName = ((MethodCallExpression)online.Body).Method.Name;
if (IsCached(proxyName, methodName)){
output = GetFromCache(proxyName, methodName);
}else{
output = online.Compile()(proxy);
}
}
}
Solution 3: using Expression as another parameter (error prone).
public static class Invoker
{
public static void invoke(Proxy proxy,Func<Proxy,string> online, Expression<Func<Proxy,string>> online2){
var methodName = ((MethodCallExpression)online2.Body).Method.Name;
if (IsCached(proxyName, methodName)){
output = GetFromCache(proxyName, methodName);
}else{
output = online(proxy);
}
}
}
Do you know any other better way to inspect and get the methodName the Invoker needs?
NOTE:
I'm not searching a caching mechanism for the online function result because I already have it.
The only problem is that this cache requires the proxy methodName invoked in the Func delegate.
You need an expression to parse the method's call name, but you can introduce some kind of two-level cache: one for the actual method call (which does not expire), and one for the method's call result (which may expire).
I think, your second solution goes into the right direction; just compile the expression only once.
public static class Invoker {
public static void Invoke(Proxy proxy, Expression<Func<Proxy,string>> online) {
var methodName = ((MethodCallExpression)online.Body).Method.Name;
if (IsCached(proxyName, methodName)) {
output = GetFromCache(proxyName, methodName);
} else {
if (IsFuncCached(methodName)) {
func = GetFuncFromCache(methodName);
} else {
func = online.Compile();
// add func to "func cache"...
}
output = func(proxy);
}
}
}
I tried to adapt your code as an example, I hope it makes sense.
I have recently implemented a solution for inspecting IL instructions of a CLR method.
You can use it like this:
using System;
using System.Linq;
using Reflection.IL;
namespace StackOverflow
{
class Program
{
static void Main(string[] args)
{
var proxy = new Proxy();
Invoker.invoke(proxy, p => p.formatSomething("Dumb test"));
}
}
public class Proxy
{
public string formatSomething(string input)
{
return String.Format("-===={0}====-", input);
}
}
public static class Invoker
{
public static void invoke(Proxy proxy, Func<Proxy, string> online)
{
//Some caching logic that require the name of the method
//invoked on the proxy (in this specific case "formatSomething")
var methodName = online.GetCalledMethods().First().Name;
Console.WriteLine(methodName);
}
}
}
Note that the code is not thoroughly tested nor documented, but I think it should serve your needs. Here it is:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
namespace Reflection.IL
{
public struct ILInstruction
{
public OpCode Code { get; private set; }
public object Operand { get; private set; }
internal ILInstruction(OpCode code, object operand)
: this()
{
this.Code = code;
this.Operand = operand;
}
public int Size
{
get { return this.Code.Size + GetOperandSize(this.Code.OperandType); }
}
public override string ToString()
{
return this.Operand == null ? this.Code.ToString() : string.Format(CultureInfo.InvariantCulture, "{0} {1}", this.Code, this.Operand);
}
private static int GetOperandSize(OperandType operandType)
{
switch (operandType)
{
case OperandType.InlineBrTarget:
case OperandType.InlineField:
case OperandType.InlineI:
case OperandType.InlineMethod:
case OperandType.InlineSig:
case OperandType.InlineString:
case OperandType.InlineSwitch:
case OperandType.InlineTok:
case OperandType.InlineType:
return sizeof(int);
case OperandType.InlineI8:
return sizeof(long);
case OperandType.InlineNone:
return 0;
case OperandType.InlineR:
return sizeof(double);
case OperandType.InlineVar:
return sizeof(short);
case OperandType.ShortInlineBrTarget:
case OperandType.ShortInlineI:
case OperandType.ShortInlineVar:
return sizeof(byte);
case OperandType.ShortInlineR:
return sizeof(float);
default:
throw new InvalidOperationException();
}
}
}
public sealed class MethodBodyIL : IEnumerable<ILInstruction>
{
private readonly MethodBase method;
public MethodBodyIL(MethodBase method)
{
if (method == null)
throw new ArgumentNullException("method");
this.method = method;
}
public Enumerator GetEnumerator()
{
var body = this.method.GetMethodBody();
return new Enumerator(this.method.Module, this.method.DeclaringType.GetGenericArguments(), this.method.GetGenericArguments(), body.GetILAsByteArray(), body.LocalVariables);
}
IEnumerator<ILInstruction> IEnumerable<ILInstruction>.GetEnumerator()
{
return this.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
public struct Enumerator : IEnumerator<ILInstruction>
{
private static readonly IDictionary<short, OpCode> codes = typeof(OpCodes).FindMembers(MemberTypes.Field, BindingFlags.Public | BindingFlags.Static, (m, criteria) => ((FieldInfo)m).FieldType == typeof(OpCode), null).Cast<FieldInfo>().Select(f => (OpCode)f.GetValue(null)).ToDictionary(c => c.Value);
private readonly Module module;
private readonly Type[] genericTypeArguments, genericMethodArguments;
private readonly byte[] il;
private readonly IList<LocalVariableInfo> localVariables;
private int offset;
private ILInstruction current;
internal Enumerator(Module module, Type[] genericTypeArguments, Type[] genericMethodArguments, byte[] il, IList<LocalVariableInfo> localVariables)
{
this.module = module;
this.genericTypeArguments = genericTypeArguments;
this.genericMethodArguments = genericMethodArguments;
this.il = il;
this.localVariables = localVariables;
this.offset = 0;
this.current = default(ILInstruction);
}
public ILInstruction Current
{
get { return this.current; }
}
public bool MoveNext()
{
if (this.offset < this.il.Length)
{
this.current = this.ReadInstruction();
return true;
}
else
{
this.current = default(ILInstruction);
return false;
}
}
public void Reset()
{
this.offset = 0;
this.current = default(ILInstruction);
}
public void Dispose()
{
this.offset = this.il.Length;
this.current = default(ILInstruction);
}
private ILInstruction ReadInstruction()
{
var code = this.ReadCode();
return new ILInstruction(code, this.ReadOperand(code.OperandType));
}
private OpCode ReadCode()
{
var code = codes[this.ReadByte()];
if (code.OpCodeType == OpCodeType.Prefix)
code = codes[(short)(code.Value << 8 | this.ReadByte())];
return code;
}
private object ReadOperand(OperandType operandType)
{
switch (operandType)
{
case OperandType.InlineBrTarget:
case OperandType.InlineI:
case OperandType.InlineSwitch:
return this.ReadInt32();
case OperandType.InlineField:
case OperandType.InlineMethod:
case OperandType.InlineTok:
case OperandType.InlineType:
return this.ReadMember();
case OperandType.InlineI8:
return this.ReadInt64();
case OperandType.InlineNone:
return null;
case OperandType.InlineR:
return this.ReadDouble();
case OperandType.InlineSig:
return this.ReadSignature();
case OperandType.InlineString:
return this.ReadString();
case OperandType.InlineVar:
return this.ReadLocalVariable();
case OperandType.ShortInlineBrTarget:
case OperandType.ShortInlineI:
return this.ReadByte();
case OperandType.ShortInlineR:
return this.ReadSingle();
case OperandType.ShortInlineVar:
return this.ReadLocalVariableShort();
default:
throw new InvalidOperationException();
}
}
private byte ReadByte()
{
var value = this.il[this.offset];
++this.offset;
return value;
}
private short ReadInt16()
{
var value = BitConverter.ToInt16(this.il, this.offset);
this.offset += sizeof(short);
return value;
}
private int ReadInt32()
{
var value = BitConverter.ToInt32(this.il, this.offset);
this.offset += sizeof(int);
return value;
}
private long ReadInt64()
{
var value = BitConverter.ToInt64(this.il, this.offset);
this.offset += sizeof(long);
return value;
}
private float ReadSingle()
{
var value = BitConverter.ToSingle(this.il, this.offset);
this.offset += sizeof(float);
return value;
}
private double ReadDouble()
{
var value = BitConverter.ToDouble(this.il, this.offset);
this.offset += sizeof(double);
return value;
}
private MemberInfo ReadMember()
{
return this.module.ResolveMember(this.ReadInt32(), this.genericTypeArguments, this.genericMethodArguments);
}
private byte[] ReadSignature()
{
return this.module.ResolveSignature(this.ReadInt32());
}
private string ReadString()
{
return this.module.ResolveString(this.ReadInt32());
}
private LocalVariableInfo ReadLocalVariable()
{
return this.localVariables[this.ReadInt16()];
}
private LocalVariableInfo ReadLocalVariableShort()
{
return this.localVariables[this.ReadByte()];
}
object IEnumerator.Current
{
get { return this.Current; }
}
}
}
public static class ILHelper
{
public static MethodBodyIL GetIL(this MethodBase method)
{
return new MethodBodyIL(method);
}
public static IEnumerable<MethodBase> GetCalledMethods(this Delegate methodPtr)
{
if (methodPtr == null)
throw new ArgumentNullException("methodPtr");
foreach (var instruction in methodPtr.Method.GetIL())
if (IsMethodCall(instruction.Code))
yield return (MethodBase)instruction.Operand;
}
private static bool IsMethodCall(OpCode code)
{
return code == OpCodes.Call || code == OpCodes.Calli || code == OpCodes.Callvirt;
}
}
}
You can use method.Name to get the calling method name.
public static class Invoker
{
public static void invoke(Proxy proxy, Func<Proxy, string> online)
{
//Some caching logic that require the name of the method
//invoked on the proxy (in this specific case "formatSomething")
var methodName = online.Method.Name;
}
}
https://msdn.microsoft.com/en-us/library/system.multicastdelegate%28v=vs.110%29.aspx
Check the following code. If you want to get the method FULL_NAME then write the following in the first line #define FULL_NAME
public class Cache
{
private const uint DefaultCacheSize = 100;
private readonly Dictionary<string, object> _cache = new Dictionary<string, object>();
private readonly object _cacheLocker = new object();
private readonly uint _cacheSize;
public Cache(uint cacheSize = DefaultCacheSize)
{
_cacheSize = cacheSize;
}
public uint CacheSize
{
get { return _cacheSize; }
}
public TValue Resolve<TObj, TValue>(TObj item, Func<TObj, TValue> func, [CallerMemberName] string key = "")
{
#if FULL_NAME
var stackTrace = new StackTrace();
var method = stackTrace.GetFrame(1).GetMethod();
key = string.Format("{0}_{1}",
method.DeclaringType == null ? string.Empty : method.DeclaringType.FullName,
method.Name);
#endif
return CacheResolver(item, func, key);
}
private TValue CacheResolver<TObj, TValue>(TObj item, Func<TObj, TValue> func, string key)
{
object res;
if (_cache.TryGetValue(key, out res) && res is TValue)
{
return (TValue) res;
}
TValue result = func(item);
lock (_cacheLocker)
{
_cache[key] = result;
if (_cache.Keys.Count > DefaultCacheSize)
{
_cache.Remove(_cache.Keys.First());
}
}
return result;
}
}
And the usage(from a Form object):
private void CacheTest()
{
var cache = new Cache();
var text = cache.Resolve<Form, string>(this, f => f.Text);
}
I hope it helps you.
EDIT I tested using expressions without performance issues, takes about ~25ms the first time. You can adapt it to Cache class in order to extract the method call expression of the param Expression<Func<T, T1>>.
private string GetExpressionMethodCallName<T, T1>(Expression<Func<T, T1>> exp)
{
var mce = exp.Body as MethodCallExpression;
if (mce == null)
throw new InvalidOperationException("invalid expression");
return mce.Method.Name;
}
I try to create an object model for the following problem.
I need a folder object (comparable to directory folders). Each folder can contain additional sub folders and in addition parameter objects (comparable to files). In addition, each parameter needs to know in which folder it resides. This is easy so far. So I implemented the following working solution.
I have a base object, that can either be inherited to a folder or a parameter:
[Serializable()]
public class Entry
{
public Func<string> GetPath;
public string Path
{
get
{
if (GetPath == null) return string.Empty;
return GetPath.Invoke();
}
}
}
Now I created a FolderEntry, that inherits from Entry and supports adding new sub entries by implementing IList<>.
[Serializable()]
class FolderEntry : Entry, IList<Entry>
{
private readonly List<Entry> _entries;
public FolderEntry()
{
_entries = new List<Entry>();
}
public string FolderName { get; set; }
private void SetPathDelegate(Entry entry)
{
if (entry.GetPath != null) throw new ArgumentException("entry already assigned");
entry.GetPath = () =>
{
if (GetPath == null || string.IsNullOrEmpty(GetPath.Invoke())) return FolderName;
return GetPath.Invoke() + "|" + FolderName;
};
}
public void Add(Entry item)
{
SetPathDelegate(item);
_entries.Add(item);
}
[...]
}
To support Undo/Redo functionality, I made all classes serializable by adding the Serializable-Attribute.
This serialization is working so far using the following test:
var folderA = new FolderEntry();
var folderB = new FolderEntry();
folderA.Add(folderB);
var serializer = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
var memStream = new System.IO.MemoryStream();
serializer.Serialize(memStream, folderA);
Now here’s my problem. There is in addition the need that each parameter knows its index inside the hosting list. I changed my Entry-object to have a property Index and a delegate GetIndex in the same manner as Path and GetPath before:
[Serializable()]
public class Entry
{
public Func<string> GetPath;
public string Path
{
get
{
if (GetPath == null) return string.Empty;
return GetPath.Invoke();
}
}
public Func<int> GetIndex;
public int Index
{
get
{
if (GetIndex == null) return -1;
return GetIndex.Invoke();
}
}
}
Inside the SetPathDelegate of the Folder-object I assigned the new delegate
private void SetPathDelegate(Entry entry)
{
if (entry.GetPath != null) throw new ArgumentException("entry already assigned");
if (entry.GetIndex != null) throw new ArgumentException("entry already assigned");
entry.GetPath = () =>
{
if (GetPath == null || string.IsNullOrEmpty(GetPath.Invoke())) return FolderName;
return GetPath.Invoke() + "|" + FolderName;
};
entry.GetIndex = () =>
{
return _entries.IndexOf(entry);
};
}
If I try to serialize this, I get an expection that my „FolderEntry+<>c__DisplayClass2“ in Assembly… is not marked as serializable. I can’t see an obvious difference between GetPath and GetIndex. To narrow it down, I replaced content of the created GetIndex delegate in SetPathDelegate from
entry.GetIndex = () =>
{
return _entries.IndexOf(entry);
};
To
entry.GetIndex = () =>
{
return -1;
};
To my astonishment this is serializable again. Why doesn‘t cause my GetPath delegate any problems regarding the serialization but my GetIndex delegate does?
The problem is the anonymous function that you assign to GetIndex. At runtime, a new type is created, which is not marked as serializable.
According to this post, you should set a SurrogateSelector for the formatter (with some caveats, read the article in detail):
formatter.SurrogateSelector = new UnattributedTypeSurrogateSelector();
I'me pasting here the classes from the article, for future reference and in order to make the answer thorough.
public class UnattributedTypeSurrogate : ISerializationSurrogate
{
private const BindingFlags publicOrNonPublicInstanceFields =
BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public;
public void GetObjectData(object obj,
SerializationInfo info, StreamingContext context)
{
var type = obj.GetType();
foreach (var field in type.GetFields(publicOrNonPublicInstanceFields))
{
var fieldValue = field.GetValue(obj);
var fieldValueIsNull = fieldValue != null;
if (fieldValueIsNull)
{
var fieldValueRuntimeType = fieldValue.GetType();
info.AddValue(field.Name + "RuntimeType",
fieldValueRuntimeType.AssemblyQualifiedName);
}
info.AddValue(field.Name + "ValueIsNull", fieldValueIsNull);
info.AddValue(field.Name, fieldValue);
}
}
public object SetObjectData(object obj,
SerializationInfo info, StreamingContext context, ISurrogateSelector selector)
{
var type = obj.GetType();
foreach (var field in type.GetFields(publicOrNonPublicInstanceFields))
{
var fieldValueIsSerializable = info.GetBoolean(field.Name + "ValueIsNull");
if (fieldValueIsSerializable)
{
var fieldValueRuntimeType = info.GetString(field.Name + "RuntimeType");
field.SetValue(obj,
info.GetValue(field.Name, Type.GetType(fieldValueRuntimeType)));
}
}
return obj;
}
}
public class UnattributedTypeSurrogateSelector : ISurrogateSelector
{
private readonly SurrogateSelector innerSelector = new SurrogateSelector();
private readonly Type iFormatter = typeof(IFormatter);
public void ChainSelector(ISurrogateSelector selector)
{
innerSelector.ChainSelector(selector);
}
public ISerializationSurrogate GetSurrogate(
Type type, StreamingContext context, out ISurrogateSelector selector)
{
if (!type.IsSerializable)
{
selector = this;
return new UnattributedTypeSurrogate();
}
return innerSelector.GetSurrogate(type, context, out selector);
}
public ISurrogateSelector GetNextSelector()
{
return innerSelector.GetNextSelector();
}
}
I am trying to pass a method as an action, but it seems that that the casting is not per say.
This is how I am doing it:
public class RequestHandler<T> where T : struct
{
public enum EmployeeRequests { BasicDetails, DependentsAndEmergencyContacts , MedicalHistory }
protected Dictionary<T, Action> handlers = new Dictionary<T, Action>();
protected EmployeeManagement empMgmnt = new EmployeeManagement();
public void InitializeHandler(int employeeID)
{
this.AddHandler(EmployeeRequests.BasicDetails, () => empMgmnt.GetEmployeeBasicDetails(0));
}
public void AddHandler(T caseValue, Action action)
{
handlers.Add(caseValue, action);
}
public void RemoveHandler(T caseValue)
{
handlers.Remove(caseValue);
}
public void ExecuteHandler(T actualValue)
{
ExecuteHandler(actualValue, Enumerable.Empty<T>());
}
public void ExecuteHandler(T actualValue, IEnumerable<T> ensureExistence)
{
foreach(var val in ensureExistence)
if (!handlers.ContainsKey(val))
throw new InvalidOperationException("The case " + val.ToString() + " is not handled.");
handlers[actualValue]();
}
}
And this is my function that I am passing as a parameter:
public object GetEmployeeBasicDetails(int employeeID)
{
return new { First_Name = "Mark", Middle_Initial = "W.", Last_Name = "Rooney"};
}
I am getting this error:
Overloaded method has some invalid arguments.
UPDATE
This is how I manage to get around this:
public static class RequestHandler
{
public enum EmployeeRequests { BasicDetails = 0, DependentsAndEmergencyContacts = 1 , MedicalHistory = 2 }
private static Dictionary<EmployeeRequests, Func<object>> handlers = new Dictionary<EmployeeRequests, Func<object>>();
public static void InitializeHandler(int employeeID)
{
Func<object> EmpBasicDetails = delegate { return EmployeeManagement.GetEmployeeBasicDetails(0); };
AddHandler(EmployeeRequests.BasicDetails, EmpBasicDetails);
}
private static void AddHandler(EmployeeRequests caseValue, Func<object> empBasicAction)
{
handlers.Add(caseValue, empBasicAction);
}
public static void RemoveHandler(int caseValue)
{
var value = (EmployeeRequests)Enum.Parse(typeof(EmployeeRequests), caseValue.ToString());
handlers.Remove(value);
}
public static object ExecuteHandler(int actualValue)
{
var request = (EmployeeRequests)Enum.Parse(typeof(EmployeeRequests), actualValue.ToString());
return handlers[(EmployeeRequests)request]();
}
}
You cannot pass a value-returning method as an Action, because Action<T> must take a parameter T and return nothing (i.e. void).
You can work around this by passing a lambda that calls your method and ignores its output:
Action empBasicAction = () => GetEmployeeBasicDetails(0);
I need to call SetSettings() and using the 3 elements in splitSettings, set EncodeAudio to False.
How would I go about doing that? Convert the property of a object to who's name I have in a string.
I realize I could do with with a switch statement of all my settings but there has to be a more dynamic way to go about doing this.
namespace SettingsLib
{
public class Settings
{
public Boolean EncodeAudio { get; set; }
}
}
namespace Service
{
void SetSettings()
{
string[] splitSettings = { "SettingsLib.Settings", "EncodeAudio", "False" };
// Need to set EncodeAudio to False in SettingsLib.Settings
}
}
Yes I have a instance of Settings
Say:
Settings settingManager = new Settings();
I am trying to do is dynamically set EncodeAudo to False by using elements of splitSettings
settingManager.EncodeAudio = False;
Thanks to the help of TBohnen.jnr
I came to this answer:
public void setProperty(object containingObject, string propertyName, object newValue)
{
foreach (PropertyInfo p in containingObject.GetType().GetProperties())
{
if (p.Name == propertyName)
{
p.SetValue(containingObject, Convert.ChangeType(newValue, p.PropertyType), null);
}
}
}
EDIT Tested it with int, bool, double and string and it worked, also added a check to make sure that the property exists and throws an exception of it doesn't (Might want to change Exception type)
EDIT 2: Temporary solution, will add more typenames to the convert method or alternatively if somebody can suggest a more dynamic way of casting it (If not then I assume you will have to know all of the types that will be used)?
EDIT3 Stole the convert method from another answer in question (Chris Taylor ), thanks :-)
public void setProperty(object containingObject, string propertyName, object newValue)
{
if (containingObject.GetType().GetProperties().Count(c => c.Name == propertyName) > 0)
{
var type = containingObject.GetType().GetProperties().First(c => c.Name == propertyName).PropertyType;
object val = Convert(type,(string)newValue);
containingObject.GetType().InvokeMember(propertyName, BindingFlags.SetProperty, null, containingObject, new object[] { val });
}
else
{
throw new KeyNotFoundException("The property: " + propertyName + " was not found in: " + containingObject.GetType().Name);
}
}
public object convert(System.Type type, string value)
{
return Convert.ChangeType(value, type);
}
Taken from http://www.haslo.ch/blog/setproperty-and-getproperty-with-c-reflection/
Was interested to see if this works, create a quick test:
class testSettings
{
public bool SetBool { get; set; }
public void setProperty(object containingObject, string propertyName, object newValue)
{
if (containingObject.GetType().GetProperties().Count(c => c.Name == propertyName) > 0)
{
containingObject.GetType().InvokeMember(propertyName, BindingFlags.SetProperty, null, containingObject, new object[] { newValue });
}
else
{
throw new KeyNotFoundException("The property: " + propertyName + " was not found in: " + containingObject.GetType().Name);
}
}
}
static void Main(string[] args)
{
testSettings ts = new testSettings();
ts.SetBool = false;
ts.setProperty(ts, "SetBool", true);
Console.WriteLine(ts.SetBool.ToString());
Console.Read();
}
The output is true, not entirely sure if it will convert all types correctly though.
As others have mentioned, you should consider making your SettingsLib class static. And you might also need to handle the conversion of values from strings to the target types. Here is a simple example how this would work.
namespace Service
{
class Program
{
static void Main(string[] args)
{
string[] splitSettings = { "SettingsLib.Settings", "EncodeAudio", "False" };
SetProperty(splitSettings[0], splitSettings[1], splitSettings[2]);
}
static void SetProperty(string typeName, string propertyName, object value)
{
var type = Type.GetType(typeName);
if (type == null)
{
throw new ArgumentException("Unable to get type", "typeName");
}
var pi = type.GetProperty(propertyName);
if (pi == null)
{
throw new ArgumentException("Unable to find property on type", "propertyName");
}
object propertyValue = value;
if (propertyValue != null)
{
// You might need more elaborate testing here to ensure that you can handle
// all the various types, you might need to special case some types here
// but this will work for the basics.
if (pi.PropertyType != propertyValue.GetType())
{
propertyValue = Convert.ChangeType(propertyValue, pi.PropertyType);
}
}
pi.SetValue(null, propertyValue, null);
}
}
}
namespace SettingsLib
{
public static class Settings
{
public static bool EncodeAudio { get; set; }
}
}
Maybe you should mark your settable properties as static and then try to set the values using Reflection:
namespace SettingsLib
{
public static class Settings
{
public static bool EncodeAudio { get; set; }
}
}
namespace Service
{
void SetSettings()
{
string[] splitSettings = { "SettingsLib.Settings", "EncodeAudio", "False" };
dynamic property = Type.GetType(splitSettings[0]).GetProperty(splitSettings[1]);
property = splitSettings[2];
}
}