Get static List from AppDomain - c#

I want to get access to a static List of Class2, which will be created in Class1.
Class1 is loaded in an AppDomain and loads Class2 in an other AppDomain.
But if I want to get access to the static List in Class2, I get two different Lists.
I think I have to access to the same AppDomain in Class1 to get Class2, but how I can achieve this, if the objects of Class1 are in different AppDomains?
By the way: It is not neccessary to put Class2 in an other AppDomain, but I thought I could get the same object if I would do it.
Here is my code:
public class Class1 : MarshalByRefObject
{
Class2 class2;
public Class2 Class2
{
get { return class2; }
set { class2 = value; }
}
public Class1()
{
AppDomain adc2 = AppDomain.CreateDomain("adc2");
class2 = (Class2)adc2.CreateInstanceAndUnwrap(typeof(Class2).Assembly.FullName, typeof(Class2).FullName);
}
}
public class Class2 : MarshalByRefObject
{
static List<int> myIntegers = new List<int>();
public static List<int> MyIntegers
{
get { return Class2.myIntegers; }
set { Class2.myIntegers = value; }
}
public void AddInteger(int integer)
{
myIntegers.Add(integer);
}
public override string ToString()
{
StringBuilder sb = new StringBuilder();
foreach (int integer in myIntegers)
{
sb.AppendLine(integer.ToString());
}
return sb.ToString();
}
}
class Program
{
static void Main(string[] args)
{
Type type1 = typeof(Class1);
AppDomain ad1 = AppDomain.CreateDomain("ad1");
Class1 ad1t1 = (Class1)ad1.CreateInstanceFromAndUnwrap(type1.Assembly.Location, type1.FullName);
AppDomain ad2 = AppDomain.CreateDomain("ad2");
Class1 ad2t1 = (Class1)ad2.CreateInstanceFromAndUnwrap(type1.Assembly.Location, type1.FullName);
ad1t1.Class2.AddInteger(0);
ad2t1.Class2.AddInteger(1);
Console.WriteLine(ad1t1.Class2.ToString()); //Output: 0
Console.WriteLine(ad2t1.Class2.ToString()); //Output: 1
//
Console.ReadKey();
}
}
Edit
Ok I found out that my plugin loader is to blame.
Can someone say why you cant work across appdomains if you will use different Pluginloaders (or at least one loader)?
If all files are in one Assembly it will work (increment is 3). In my scenario (many different assemblies) I only get 1,1,1
If someone need more information to help me, feel free to ask for it.
Example 1 (Each Instance count for itself):
Assembly: Main
PluginLoader.PluginLoader<IPlugin> pluginLoader1 = new PluginLoader.PluginLoader<IPlugin>(new DirectoryInfo(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location)));
IPlugin cl1 = pluginLoader1.Activate("MyLibrary.dll", "MyLibrary.Class1");
PluginLoader.PluginLoader<IPlugin> pluginLoader2 = new PluginLoader.PluginLoader<IPlugin>(new DirectoryInfo(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location)));
IPlugin cl3 = pluginLoader2.Activate("MyLibrary2.dll", "MyLibrary2.Class3");
//Increment() increases a static variable starting by 0
cl1.Increment();
Console.WriteLine(cl1.ToString()); //Output: 1
cl3.Increment();
Console.WriteLine(cl3.ToString()); //Output: 1
ClassLibrary2.Class2 class2 = new ClassLibrary2.Class2();
class2.Increment();
Console.WriteLine(class2.ToString()); //Output: 1
Assembly: ClassLibrary2
[Serializable]
public class Class2 : IPlugin
{
public Class2() { }
public override string ToString()
{
return incrementer.ToString();
}
static int incrementer = 0;
public void Increment()
{
incrementer++;
}
}
Assembly: MyLibrary
public class Class1 : MarshalByRefObject, IPlugin
{
Class2 class2;
public Class2 Class2
{
get { return class2; }
set { class2 = value; }
}
public Class1()
{
PluginLoader.PluginLoader<Class2> pluginLoader = new PluginLoader.PluginLoader<Class2>(new DirectoryInfo(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location)));
class2 = pluginLoader.Activate("ClassLibrary2.dll", "ClassLibrary2.Class2");
//AppDomain adc2 = AppDomain.CreateDomain("adc2");
//class2 = (Class2)adc2.CreateInstanceAndUnwrap(typeof(Class2).Assembly.FullName, typeof(Class2).FullName);
}
public void Increment()
{
this.class2.Increment();
}
public override string ToString()
{
return AppDomain.CurrentDomain.FriendlyName+": "+ this.class2.ToString();
}
}
Assembly: MyLibrary2
public class Class3 : MarshalByRefObject, IPlugin
{
Class2 class2;
public Class2 Class2
{
get { return class2; }
set { class2 = value; }
}
public Class3()
{
PluginLoader.PluginLoader<Class2> pluginLoader = new PluginLoader.PluginLoader<Class2>(new DirectoryInfo(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location)));
class2 = pluginLoader.Activate("ClassLibrary2.dll", "ClassLibrary2.Class2");
//AppDomain adc2 = AppDomain.CreateDomain("adc2");
//class2 = (Class2)adc2.CreateInstanceAndUnwrap(typeof(Class2).Assembly.FullName, typeof(Class2).FullName);
}
public void Increment()
{
this.class2.Increment();
}
public override string ToString()
{
return AppDomain.CurrentDomain.FriendlyName + ": " + this.class2.ToString();
}
}
Assembly: PluginInterface
public interface IPlugin
{
void Increment();
}
Assembly: PluginLoader
public class PluginLoader<T> where T : IPlugin
{
//Here are placed all Fields
#region Fields
string path;
System.AppDomain appDomain;
#endregion
//Here are placed all Properties
#region Properties
public List<KeyValuePair<String, String>> Plugins
{
get { return (List<KeyValuePair<String, String>>)appDomain.GetData("Plugins"); }
}
#endregion
//Here are placed all Constructors
#region Constructors
public PluginLoader(DirectoryInfo path)
{
this.path = path.FullName;
if (!path.Exists)
path.Create();
AppDomainSetup appDomainSetup = new AppDomainSetup();
appDomainSetup.AppDomainInitializer = new AppDomainInitializer(GetInterfaceTypes);
appDomainSetup.AppDomainInitializerArguments = new string[] { this.path };
appDomain = AppDomain.CreateDomain(typeof(T).Name, null, appDomainSetup);
}
#endregion
#region Methods
private static void GetInterfaceTypes(string[] args)
{
AppDomain appDomain = System.AppDomain.CurrentDomain;
string[] files = Directory.GetFiles(args[0], "*.dll", SearchOption.AllDirectories);
List<KeyValuePair<String, String>> infos = new List<KeyValuePair<String, String>>();
foreach (string file in files)
{
try
{
Assembly asm = Assembly.LoadFrom(file);
foreach (Type type in asm.GetTypes())
{
if (typeof(T).IsAssignableFrom(type))
infos.Add(new KeyValuePair<string, string>(file, type.FullName));
}
}
catch (Exception ex)
{
}
}
appDomain.SetData("Plugins", infos);
}
public virtual T Activate(String assemblyFile, String type, params object[] args)
{
try
{
T instance = (T)this.appDomain.CreateInstanceFromAndUnwrap(Path.Combine(this.path, Path.GetFileName(assemblyFile)), type, args);
return instance;
}
catch (Exception ex)
{
throw ex;
}
}
#endregion
}
Example 2 (Each instance count the same increment variable):
Put all classes in one assembly.
public interface IPlugin
{
void Increment();
}
[Serializable]
public class Class1 : IPlugin
{
public Class1() { }
static int incrementer = 0;
public void Increment()
{
incrementer++;
}
public override string ToString()
{
return incrementer.ToString();
}
}
class PluginLoader<T> where T : IPlugin
{
//Here are placed all Fields
#region Fields
string path;
System.AppDomain appDomain;
#endregion
//Here are placed all Properties
#region Properties
public List<KeyValuePair<String, String>> Plugins
{
get { return (List<KeyValuePair<String, String>>)appDomain.GetData("Plugins"); }
}
#endregion
//Here are placed all Constructors
#region Constructors
public PluginLoader(DirectoryInfo path)
{
this.path = path.FullName;
if (!path.Exists)
path.Create();
AppDomainSetup appDomainSetup = new AppDomainSetup();
appDomainSetup.AppDomainInitializer = new AppDomainInitializer(GetInterfaceTypes);
appDomainSetup.AppDomainInitializerArguments = new string[] { this.path };
appDomain = AppDomain.CreateDomain(typeof(T).Name, null, appDomainSetup);
}
#endregion
#region Methods
private static void GetInterfaceTypes(string[] args)
{
AppDomain appDomain = System.AppDomain.CurrentDomain;
string[] files = Directory.GetFiles(args[0], "*.dll", SearchOption.AllDirectories);
List<KeyValuePair<String, String>> infos = new List<KeyValuePair<String, String>>();
foreach (string file in files)
{
try
{
Assembly asm = Assembly.LoadFrom(file);
foreach (Type type in asm.GetTypes())
{
if (typeof(T).IsAssignableFrom(type))
infos.Add(new KeyValuePair<string, string>(file, type.FullName));
}
}
catch (Exception ex)
{
}
}
appDomain.SetData("Plugins", infos);
}
public virtual T Activate(String assemblyFile, String type, params object[] args)
{
try
{
T instance = (T)this.appDomain.CreateInstanceFromAndUnwrap(Path.Combine(this.path, Path.GetFileName(assemblyFile)), type, args);
return instance;
}
catch (Exception ex)
{
throw ex;
}
}
#endregion
}
class Program
{
static void Main(string[] args)
{
string file = System.Reflection.Assembly.GetExecutingAssembly().Location;
string path = Path.GetDirectoryName(file);
PluginLoader<IPlugin> pluginLoader1 = new PluginLoader<IPlugin>(new DirectoryInfo(path));
IPlugin cl1 = pluginLoader1.Activate(file, "AppDomainCheck.Class1");
PluginLoader<IPlugin> pluginLoader2 = new PluginLoader<IPlugin>(new DirectoryInfo(path));
IPlugin cl3 = pluginLoader1.Activate(file, "AppDomainCheck.Class1");
cl1.Increment();
Console.WriteLine(cl1.ToString()); //Output: 1
cl3.Increment();
Console.WriteLine(cl3.ToString()); //Output: 2
Console.ReadKey();
}
}

Static variables are limited to the current App domain. If you have N different app domains, then you have N different values for a static property.
In C# Language Specification 5.0 :
10.5.1 A static field is not part of a specific instance; instead, it is shared amongst all instances of a closed type (ยง4.4.2). No matter how many instances of a closed class type are created, there is only ever one copy of a static field for the associated application domain.

Related

Multithread Singeleton Substitution with NSubstitute

I have a public method ValidateWords inside FooService.To test the ValidateWord method, I created IAppSettingWrapper and AppSettingWrapper which returns the Instance of AppSettings.
Inside the test method, I want to substitute NotAllowedWords using NSubstitute. However, it throws an object reference exception. Is there any way for substitution? If it's not possible, how can I refactor my static instance?
public sealed class AppSettings
{
private static object _lockObject = new object();
private static volatile AppSettings? _instance;
private static DateTime _cacheTime;
private Settings[] _settings;
public AppSettings()
{
try
{
_settings = GetSettings();
}
catch { }
}
public static AppSettings Instance
{
get
{
lock (_lockObject)
{
if (_instance == null)
{
_instance = new AppSettings();
}
}
return _instance;
}
}
public List<string> NotAllowedWords
{
get
{
return new List<string>() {
"index",
"change"
};
}
}
public T GetValues<T>(string key,T defaultValue)
{
T result = defaultValue;
var settings = _settings.Where(i => i.Key == key).FirstOrDefault();
result = (T)Convert.ChangeType(settings.Value, typeof(T));
return result;
}
private Settings[]? GetSettings()
{
//gets data from web services
return base.Channel.GetSettings();
}
}
public class Settings
{
public string Key { get; set; }
public string Value { get; set; }
}
public interface IAppSettingsWrapper
{
public AppSettings Instance();
}
public class AppSettingsWrapper : IAppSettingsWrapper
{
public AppSettings Instance()
{
return AppSettings.Instance;
}
}
[TestClass]
public class FooServiceTest{
private IAppSettingsWrapper _appSettingsWrapper;
[TestInitialize]
public void TestInitialize(IAppSettingsWrapper appSettingsWrapper)
{
_appSettingsWrapper = Substitute.For<IAppSettingsWrapper>();
}
private FooService CreateFooService()
{
return new FooService(_appSettingsWrapper);
}
[TestMethod]
public void Throw_Exception_When_Given_Word_Not_Allowed() {
var service = this.CreateFooService();
_appSettingsWrapper.Instance().NotAllowedWords.Returns(new List<string> { "index" });
var word = "index";
Exception ex = Assert.ThrowsException<Exception>(() => service.ValidateWords(word));
Assert.AreEqual("this word is not allowed", ex.Message);
}
}
public class FooService
{
private IAppSettingsWrapper _appSettingsWrapper;
public FooService(IAppSettingsWrapper appSettingsWrapper)
{
_appSettingsWrapper = appSettingsWrapper;
}
public void ValidateWords(string word)
{
if (_appSettingsWrapper.Instance().NotAllowedWords.Contains(word))
{
throw new Exception("this word is not allowed");
}
}
}
The AppSettings.NotAllowedWords property is not substitutable due to it not being virtual and the class being sealed. If you add NSubstitute.Analyzers to your test project it will help you find these cases. (The How NSubstitute Works documentation outlines why this is the case.)
One option is to make AppSettings implement an IAppSettings interface and inject that into FooService (rather than the wrapper). Then you can use a substitute for tests, and AppSettings.Instance for your real code.

Is it possible to get the name of the object being instantiated, from within the constructor?

Is there any way, using reflection, ConstructorInfo or System.Runtime.CompilerServices or something else, to get the variable name of the object being created?
Example:
public class MyObject
{
public MyObject()
{
// Should print "Creating funnyName"
Debug.Writeline("Creating {0}", MagicGetNameMethod());
}
}
public class MyClass
{
public MyObject funnyName = null;
public void MyMethod()
{
funnyName = new MyObject();
}
}
You can get the file path, line number and method name of the caller, which is probably more useful for timing info:
The following will print something like Creating MyMethod#c:\source\MyProgram.cs:15.
public class MyObject
{
public MyObject(
[CallerMemberName]string caller = null,
[CallerFilePathAttribute] string path = null,
[CallerLineNumber]int lineNumber = 0)
{
Console.WriteLine("Creating {0}", $"{caller}#{path}:{lineNumber}");
System.Reflection.
MethodBase.GetCurrentMethod();
}
}
public class MyClass
{
public MyObject funnyName = null;
public void MyMethod()
{
funnyName = new MyObject();
}
}

Create instances with changing class names

I want to call many tests like this.
var test8001 = new Test8001();
test8001.Execute(drv);
var test8002 = new Test8002();
test8002.Execute(drv);
var test8007 = new Test8007();
test8007.Execute(drv);
How can I automatically instantiate all test function with a int list of all test numbers?
List<int> classNameNumbers = new List<int>() { 8001, 8002, 8007 };
I need a for-loop where Execute() is called on every instance.
Edit:
The name of the type e.g. 'Test8001' should be retrieved from my integer list.
Try out the following
namespace Stackoverflow46529447
{
class Program
{
static void Main(string[] args)
{
var drv = new Drv();
var numbers = new[] {8001, 8002, 8003};
var executables = numbers.Select(x => Activator.CreateInstance(Type.GetType($"Stackoverflow46529447.Test{x:0000}")))
.OfType<IExecutable>()
.ToArray();
foreach (var executable in executables)
{
executable.Execute(drv);
}
}
}
public class Test8001 : IExecutable
{
public void Execute(Drv drv)
{
Console.WriteLine("Hello from Test 8001");
}
}
public class Test8002 : IExecutable
{
public void Execute(Drv drv)
{
Console.WriteLine("Hello from Test 8002");
}
}
public class Test8003 : IExecutable
{
public void Execute(Drv drv)
{
Console.WriteLine("Hello from Test 8003");
}
}
public interface IExecutable
{
void Execute(Drv drv);
}
public class Drv
{
}
}
This uses reflection to create instance types.

Is it possible to create a derived class from a base class constructor?

I have say 3 classes, Animal, Cat & Dog.
// calling code
var x = new Animal("Rex"); // would like this to return a dog type
var x = new Animal("Mittens"); // would like this to return a cat type
if(x.GetType() == typeof(Dog))
{
x.Bark();
}
else
{
x.Meow();
}
class Animal
{
public Animal(string name)
{
// check against some list of dog names ... find rex
// return Animal of type Dog.
// if not...
// check against some list of cat names ... find mittens
// return Animal of type Cat.
}
}
Is this possible somehow? If not is there something similar I can do?
What you are looking for is either a 'virtual constructor' (not possibe in C#) or the Factory pattern.
class Animal
{
// Factory method
public static Animal Create(string name)
{
Animal animal = null;
... // some logic based on 'name'
animal = new Zebra();
return animal;
}
}
The Factory method can also be placed in another (Factory) class. That gives better decoupling etc.
No. Basically the right fix is to use a static method which can create an instance of the right type:
var x = Animal.ForName("Rex");
var x = Animal.ForName("Mittens");
...
public abstract class Animal
{
public static Animal ForName(string name)
{
if (dogNames.Contains(name))
{
return new Dog(name);
}
else
{
return new Cat(name);
}
}
}
Or this could be an instance method in an AnimalFactory type (or whatever). That would be a more extensible approach - the factory could implement an interface, for example, and could be injected into the class which needed to create the instances. It really depends on the context though - sometimes that approach is overkill.
Basically, a new Foo(...) call always creates an instance of exactly Foo. Whereas a static method declared with a return type of Foo can return a reference to any type which is compatible with Foo.
No I dont think it is possible in the way that you want.
You could create a static class that has a method that returns an animal based on a name e.g.
static Animal CreateAnimal(string name)
{
if(catList.Contains(name))
return new Cat(name");
else if(dogList.Contains(name))
return new Dog(name);
return null;
}
The other answers show that you need to use a factory pattern but I wanted to give you a more "practical" example of how you would do it. I did exactly what you where doing, however I was working with the EPL2 printer language. When I saw X I needed to create a instance of class Rectangle, when I saw A I needed to create a instance of class Text.
(I wrote this a long time ago so I am sure some of the things I did could be improved upon).
public partial class Epl2CommandFactory
{
#region Singelton pattern
private static volatile Epl2CommandFactory m_instance;
private static object m_syncRoot = new object();
public static Epl2CommandFactory Instance
{
get
{
if (m_instance == null)
{
lock (m_syncRoot)
{
if (m_instance == null)
{
m_instance = new Epl2CommandFactory();
}
}
}
return m_instance;
}
}
#endregion
#region Constructor
private Epl2CommandFactory()
{
m_generalCommands = new Dictionary<string, Type>();
Initialize();
}
#endregion
#region Variables
private Dictionary<string, Type> m_generalCommands;
private Assembly m_asm;
#endregion
#region Helpers
private void Initialize()
{
Assembly asm = Assembly.GetAssembly(GetType());
Type[] allTypes = asm.GetTypes();
foreach (Type type in allTypes)
{
// Only scan classes that are not abstract
if (type.IsClass && !type.IsAbstract)
{
// If a class implements the IEpl2FactoryProduct interface,
// which allows retrieval of the product class key...
Type iEpl2FactoryProduct = type.GetInterface("IEpl2GeneralFactoryProduct");
if (iEpl2FactoryProduct != null)
{
// Create a temporary instance of that class...
object inst = asm.CreateInstance(type.FullName);
if (inst != null)
{
// And generate the product classes key
IEpl2GeneralFactoryProduct keyDesc = (IEpl2GeneralFactoryProduct)inst;
string key = keyDesc.GetFactoryKey();
m_generalCommands.Add(key, type);
inst = null;
}
}
}
}
m_asm = asm;
}
#endregion
#region Methods
public IEpl2Command CreateEpl2Command(string command)
{
if (command == null)
throw new NullReferenceException("Invalid command supplied, must be " +
"non-null.");
Type type;
if (!m_generalCommands.TryGetValue(command.Substring(0, 2), out type))
m_generalCommands.TryGetValue(command.Substring(0, 1), out type);
if (type != default(Type))
{
object inst = m_asm.CreateInstance(type.FullName, true,
BindingFlags.CreateInstance,
null, null, null, null);
if (inst == null)
throw new NullReferenceException("Null product instance. " +
"Unable to create necessary product class.");
IEpl2Command prod = (IEpl2Command)inst;
prod.CommandString = command;
return prod;
}
else
{
return null;
}
}
#endregion
}
The way the code works is I use the singleton pattern to create a factory class so people can call var command = Epl2CommandFactory.Instance.CreateEpl2Command("..."); passing in the EPL2 command string and it returns a instance of the class that represents that specific class.
During initialization I use reflection to find classes that support the IEpl2GeneralFactoryProduct interface, if the class supports the interface the factory stores the one or two letter code representing the printer command in a dictionary of types.
When you try to create the command the factory looks up the printer command in the dictionary and creates the correct class, it then passes the full command string on to that class for further processing.
Here is a copy of a command class and it's parents if you wanted to see it
Rectangle:
[XmlInclude(typeof(Rectangle))]
public abstract partial class Epl2CommandBase { }
/// <summary>
/// Use this command to draw a box shape.
/// </summary>
public class Rectangle : DrawableItemBase, IEpl2GeneralFactoryProduct
{
#region Constructors
public Rectangle() : base() { }
public Rectangle(Point startingLocation, int horozontalEndPosition, int verticalEndPosition)
: base(startingLocation)
{
HorizontalEndPosition = horozontalEndPosition;
VerticalEndPosition = verticalEndPosition;
}
public Rectangle(int x, int y, int lineThickness, int horozontalEndPosition, int verticalEndPosition)
: base(x, y)
{
LineThickness = lineThickness;
HorizontalEndPosition = horozontalEndPosition;
VerticalEndPosition = verticalEndPosition;
}
#endregion
#region Properties
[XmlIgnore]
public int LineThickness { get; set; }
[XmlIgnore]
public int HorizontalEndPosition {get; set;}
[XmlIgnore]
public int VerticalEndPosition { get; set; }
public override string CommandString
{
get
{
return String.Format("X{0},{1},{2},{3},{4}", X, Y, LineThickness, HorizontalEndPosition, VerticalEndPosition);
}
set
{
GenerateCommandFromText(value);
}
}
#endregion
#region Helpers
private void GenerateCommandFromText(string command)
{
if (!command.StartsWith(GetFactoryKey()))
throw new ArgumentException("Command must begin with " + GetFactoryKey());
string[] commands = command.Substring(1).Split(',');
this.X = int.Parse(commands[0]);
this.Y = int.Parse(commands[1]);
this.LineThickness = int.Parse(commands[2]);
this.HorizontalEndPosition = int.Parse(commands[3]);
this.VerticalEndPosition = int.Parse(commands[4]);
}
#endregion
#region Members
public override void Paint(Graphics g, Image buffer)
{
using (Pen p = new Pen(Color.Black, LineThickness))
{
g.DrawRectangle(p, new System.Drawing.Rectangle(X, Y, HorizontalEndPosition - X, VerticalEndPosition - Y));
}
}
public string GetFactoryKey()
{
return "X";
}
#endregion
}
DrawableItemBase:
public abstract class DrawableItemBase : Epl2CommandBase, IDrawableCommand
{
protected DrawableItemBase()
{
Location = new Point();
}
protected DrawableItemBase(Point location)
{
Location = location;
}
protected DrawableItemBase(int x, int y)
{
Location = new Point();
X = x;
Y = y;
}
private Point _Location;
[XmlIgnore]
public virtual Point Location
{
get { return _Location; }
set { _Location = value; }
}
[XmlIgnore]
public int X
{
get { return _Location.X; }
set { _Location.X = value; }
}
[XmlIgnore]
public int Y
{
get { return _Location.Y; }
set { _Location.Y = value; }
}
abstract public void Paint(Graphics g, Image buffer);
}
Epl2CommandBase:
public abstract partial class Epl2CommandBase : IEpl2Command
{
protected Epl2CommandBase() { }
public virtual byte[] GenerateByteCommand()
{
return Encoding.ASCII.GetBytes(CommandString + '\n');
}
public abstract string CommandString { get; set; }
}
Various Interfaces:
public interface IEpl2GeneralFactoryProduct
{
string GetFactoryKey();
}
public interface IEpl2Command
{
string CommandString { get; set; }
}
public interface IDrawableCommand : IEpl2Command
{
void Paint(System.Drawing.Graphics g, System.Drawing.Image buffer);
}

Create an alternative to serializable anonymous delegates

there have been quite some posts about this, all trying to serialize a Func delegate.
But could someone think of an alternative, when the use of the delegate is always clear?
We have a generic create command, which takes a delegate as paramater in the constructor. This delegate will create the Item for the create command:
public class CreateCommand<T> : Command
{
public T Item;
protected Func<T> Constructor;
public ClientCreateCommand(Func<T> constructor)
{
Constructor = constructor;
}
public override void Execute()
{
Item = Constructor();
}
}
The command is used like this:
var c = new CreateCommand<MyType>( () => Factory.CreateMyType(param1, param2, ...) );
History.Insert(c);
Then the History serializes the command and sends it to the server. ofc the delegate can't be serialized as is and we get an exception.
Now could someone think of a very simple Constructor class that can be serialized and does the same job than the lambda expresseion? Means it takes a list of paramters and returns an instance of type T, that we then can write somethink like this:
var constructor = new Constructor<MyType>(param1, param2, ...);
var c = new CreateCommand<MyType>(constructor);
History.Insert(c);
How would the Constructor class look like? Thanks for any ideas!
EDIT(2): I've provided a couple of complete example implementations. They are categorized below as "Implementation 1" and "Implementation 2".
Your delegate is essentially a factory. You could define a factory interface and create a class that implements that interface for your Item class. Below is an example:
public interface IFactory<T>
{
T Create();
}
[Serializable]
public class ExampleItemFactory : IFactory<T>
{
public int Param1 { get; set; }
public string Param2 { get; set; }
#region IFactory<T> Members
public Item Create()
{
return new Item(this.Param1, this.Param2);
}
#endregion
}
public class CreateCommand<T> : Command
{
public T Item;
protected IFactory<T> _ItemFactory;
public CreateCommand(IFactory<T> factory)
{
_ItemFactory = factory;
}
public override void Execute()
{
Item = _ItemFactory.Create();
}
}
You would utilize this code in the following manner:
IFactory<Item> itemFactory = new ExampleItemFactory { Param1 = 5, Param2 = "Example!" };
CreateCommand<Item> command = new CreateCommand<Item>(itemFactory);
command.Execute();
EDIT(1): The specific implementations of IFactory<T> that your application needs will be up to you. You could create specific factory classes for each class that you need, or you could create some kind of factory that dynamically creates an instance using, for example, the Activator.CreateInstance function or perhaps using some kind of Inversion of Control framework such as Spring or StructureMap.
Below is a complete example implementation that uses two factory implementations. One implementation can create any type given an array of arguments using that type's constructor with matching parameters. Another implementation creates any type that has been registered with my "Factory" class.
The Debug.Assert statements ensure that everything is behaving as intended. I ran this application without error.
Implementation 1
[Serializable]
public abstract class Command
{
public abstract void Execute();
}
public class Factory
{
static Dictionary<Type, Func<object[], object>> _DelegateCache = new Dictionary<Type, Func<object[], object>>();
public static void Register<T>(Func<object[], object> #delegate)
{
_DelegateCache[typeof(T)] = #delegate;
}
public static T CreateMyType<T>(params object[] args)
{
return (T)_DelegateCache[typeof(T)](args);
}
}
public interface IFactory<T>
{
T Create();
}
[Serializable]
public class CreateCommand<T> : Command
{
public T Item { get; protected set; }
protected IFactory<T> _ItemFactory;
public CreateCommand(IFactory<T> itemFactory)
{
this._ItemFactory = itemFactory;
}
public override void Execute()
{
this.Item = this._ItemFactory.Create();
}
}
// This class is a base class that represents a factory capable of creating an instance using a dynamic set of arguments.
[Serializable]
public abstract class DynamicFactory<T> : IFactory<T>
{
public object[] Args { get; protected set; }
public DynamicFactory(params object[] args)
{
this.Args = args;
}
public DynamicFactory(int numberOfArgs)
{
if (numberOfArgs < 0)
throw new ArgumentOutOfRangeException("numberOfArgs", "The numberOfArgs parameter must be greater than or equal to zero.");
this.Args = new object[numberOfArgs];
}
#region IFactory<T> Members
public abstract T Create();
#endregion
}
// This implementation uses the Activator.CreateInstance function to create an instance
[Serializable]
public class DynamicConstructorFactory<T> : DynamicFactory<T>
{
public DynamicConstructorFactory(params object[] args) : base(args) { }
public DynamicConstructorFactory(int numberOfArgs) : base(numberOfArgs) { }
public override T Create()
{
return (T)Activator.CreateInstance(typeof(T), this.Args);
}
}
// This implementation uses the Factory.CreateMyType function to create an instance
[Serializable]
public class MyTypeFactory<T> : DynamicFactory<T>
{
public MyTypeFactory(params object[] args) : base(args) { }
public MyTypeFactory(int numberOfArgs) : base(numberOfArgs) { }
public override T Create()
{
return Factory.CreateMyType<T>(this.Args);
}
}
[Serializable]
class DefaultConstructorExample
{
public DefaultConstructorExample()
{
}
}
[Serializable]
class NoDefaultConstructorExample
{
public NoDefaultConstructorExample(int a, string b, float c)
{
}
}
[Serializable]
class PrivateConstructorExample
{
private int _A;
private string _B;
private float _C;
private PrivateConstructorExample()
{
}
public static void Register()
{
// register a delegate with the Factory class that will construct an instance of this class using an array of arguments
Factory.Register<PrivateConstructorExample>((args) =>
{
if (args == null || args.Length != 3)
throw new ArgumentException("Expected 3 arguments.", "args");
if (!(args[0] is int))
throw new ArgumentException("First argument must be of type System.Int32.", "args[0]");
if (!(args[1] is string))
throw new ArgumentException("Second argument must be of type System.String.", "args[1]");
if (!(args[2] is float))
throw new ArgumentException("Third argument must be of type System.Single.", "args[2]");
var instance = new PrivateConstructorExample();
instance._A = (int)args[0];
instance._B = (string)args[1];
instance._C = (float)args[2];
return instance;
});
}
}
class Program
{
static void Main(string[] args)
{
var factory1 = new DynamicConstructorFactory<DefaultConstructorExample>(null);
var command1 = new CreateCommand<DefaultConstructorExample>(factory1);
var factory2 = new DynamicConstructorFactory<NoDefaultConstructorExample>(3);
factory2.Args[0] = 5;
factory2.Args[1] = "ABC";
factory2.Args[2] = 7.1f;
var command2 = new CreateCommand<NoDefaultConstructorExample>(factory2);
PrivateConstructorExample.Register(); // register this class so that it can be created by the Factory.CreateMyType function
var factory3 = new MyTypeFactory<PrivateConstructorExample>(3);
factory3.Args[0] = 5;
factory3.Args[1] = "ABC";
factory3.Args[2] = 7.1f;
var command3 = new CreateCommand<PrivateConstructorExample>(factory3);
VerifySerializability<DefaultConstructorExample>(command1);
VerifySerializability<NoDefaultConstructorExample>(command2);
VerifySerializability<PrivateConstructorExample>(command3);
}
static void VerifySerializability<T>(CreateCommand<T> originalCommand)
{
var serializer = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
using (var stream = new System.IO.MemoryStream())
{
System.Diagnostics.Debug.Assert(originalCommand.Item == null); // assert that originalCommand does not yet have a value for Item
serializer.Serialize(stream, originalCommand); // serialize the originalCommand object
stream.Seek(0, System.IO.SeekOrigin.Begin); // reset the stream position to the beginning for deserialization
// deserialize
var deserializedCommand = serializer.Deserialize(stream) as CreateCommand<T>;
System.Diagnostics.Debug.Assert(deserializedCommand.Item == null); // assert that deserializedCommand still does not have a value for Item
deserializedCommand.Execute();
System.Diagnostics.Debug.Assert(deserializedCommand.Item != null); // assert that deserializedCommand now has a value for Item
}
}
}
EDIT(2): After re-reading the question, I think I got a better idea of what the asker was really trying to get at. Essentially, we still want to take advantage of the flexibility offered by lambda expressions / anonymous delegates, but avoid the serialization issues.
Below is another example implementation that utilizes a Factory<T> class to store delegates used to return instances of type T.
Implementation 2
[Serializable]
public abstract class Command
{
public abstract void Execute();
}
[Serializable]
public abstract class CreateCommand<T> : Command
{
public T Item { get; protected set; }
}
public class Factory<T>
{
private static readonly object _SyncLock = new object();
private static Func<T> _CreateFunc;
private static Dictionary<string, Func<T>> _CreateFuncDictionary;
/// <summary>
/// Registers a default Create Func delegate for type <typeparamref name="T"/>.
/// </summary>
public static void Register(Func<T> createFunc)
{
lock (_SyncLock)
{
_CreateFunc = createFunc;
}
}
public static T Create()
{
lock (_SyncLock)
{
if(_CreateFunc == null)
throw new InvalidOperationException(string.Format("A [{0}] delegate must be registered as the default delegate for type [{1}]..", typeof(Func<T>).FullName, typeof(T).FullName));
return _CreateFunc();
}
}
/// <summary>
/// Registers a Create Func delegate for type <typeparamref name="T"/> using the given key.
/// </summary>
/// <param name="key"></param>
/// <param name="createFunc"></param>
public static void Register(string key, Func<T> createFunc)
{
lock (_SyncLock)
{
if (_CreateFuncDictionary == null)
_CreateFuncDictionary = new Dictionary<string, Func<T>>();
_CreateFuncDictionary[key] = createFunc;
}
}
public static T Create(string key)
{
lock (_SyncLock)
{
Func<T> createFunc;
if (_CreateFuncDictionary != null && _CreateFuncDictionary.TryGetValue(key, out createFunc))
return createFunc();
else
throw new InvalidOperationException(string.Format("A [{0}] delegate must be registered with the given key \"{1}\".", typeof(Func<T>).FullName, key));
}
}
}
[Serializable]
public class CreateCommandWithDefaultDelegate<T> : CreateCommand<T>
{
public override void Execute()
{
this.Item = Factory<T>.Create();
}
}
[Serializable]
public class CreateCommandWithKeyedDelegate<T> : CreateCommand<T>
{
public string CreateKey { get; set; }
public CreateCommandWithKeyedDelegate(string createKey)
{
this.CreateKey = createKey;
}
public override void Execute()
{
this.Item = Factory<T>.Create(this.CreateKey);
}
}
[Serializable]
class DefaultConstructorExample
{
public DefaultConstructorExample()
{
}
}
[Serializable]
class NoDefaultConstructorExample
{
public NoDefaultConstructorExample(int a, string b, float c)
{
}
}
[Serializable]
class PublicPropertiesExample
{
public int A { get; set; }
public string B { get; set; }
public float C { get; set; }
}
class Program
{
static void Main(string[] args)
{
// register delegates for each type
Factory<DefaultConstructorExample>.Register(() => new DefaultConstructorExample());
Factory<NoDefaultConstructorExample>.Register(() => new NoDefaultConstructorExample(5, "ABC", 7.1f));
Factory<PublicPropertiesExample>.Register(() => new PublicPropertiesExample() { A = 5, B = "ABC", C = 7.1f });
// create commands
var command1 = new CreateCommandWithDefaultDelegate<DefaultConstructorExample>();
var command2 = new CreateCommandWithDefaultDelegate<DefaultConstructorExample>();
var command3 = new CreateCommandWithDefaultDelegate<DefaultConstructorExample>();
// verify that each command can be serialized/deserialized and that the creation logic works
VerifySerializability<DefaultConstructorExample>(command1);
VerifySerializability<DefaultConstructorExample>(command2);
VerifySerializability<DefaultConstructorExample>(command3);
// register additional delegates for each type, distinguished by key
Factory<DefaultConstructorExample>.Register("CreateCommand", () => new DefaultConstructorExample());
Factory<NoDefaultConstructorExample>.Register("CreateCommand", () => new NoDefaultConstructorExample(5, "ABC", 7.1f));
Factory<PublicPropertiesExample>.Register("CreateCommand", () => new PublicPropertiesExample() { A = 5, B = "ABC", C = 7.1f });
// create commands, passing in the create key to the constructor
var command4 = new CreateCommandWithKeyedDelegate<DefaultConstructorExample>("CreateCommand");
var command5 = new CreateCommandWithKeyedDelegate<DefaultConstructorExample>("CreateCommand");
var command6 = new CreateCommandWithKeyedDelegate<DefaultConstructorExample>("CreateCommand");
// verify that each command can be serialized/deserialized and that the creation logic works
VerifySerializability<DefaultConstructorExample>(command4);
VerifySerializability<DefaultConstructorExample>(command5);
VerifySerializability<DefaultConstructorExample>(command6);
}
static void VerifySerializability<T>(CreateCommand<T> originalCommand)
{
var serializer = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
using (var stream = new System.IO.MemoryStream())
{
System.Diagnostics.Debug.Assert(originalCommand.Item == null); // assert that originalCommand does not yet have a value for Item
serializer.Serialize(stream, originalCommand); // serialize the originalCommand object
stream.Seek(0, System.IO.SeekOrigin.Begin); // reset the stream position to the beginning for deserialization
// deserialize
var deserializedCommand = serializer.Deserialize(stream) as CreateCommand<T>;
System.Diagnostics.Debug.Assert(deserializedCommand.Item == null); // assert that deserializedCommand still does not have a value for Item
deserializedCommand.Execute();
System.Diagnostics.Debug.Assert(deserializedCommand.Item != null); // assert that deserializedCommand now has a value for Item
}
}
}

Categories

Resources