Generate class and get type - c#

I want to create a new class at runtime, get the type, and create a List of that type.
For example:
void CreateNewClass(string name)
{
File file = CreateFile(name);
Type type = GetTypeOfFile(file);
List<type> someList = new List<type>();
}
void CreateFile(string name)
{
//Get a template file
//copy that file
//change some names and stuff in it
//return the file
}
Type GetTypeOfFile(File file)
{
//get the class(type) of that file
// return type
}
I do not want to generate that class in memory, I need to have that class as a file.
The file creation is not a problem.
I just need to know:
How to load the file/class and get it's type
How to create a list with the loaded type
I guess something with Reflections need to happen, but after that I'm clueless.
edit:
FYI:
I don't actually want to create a List, I need this functionallity for a Unity Project.
For those who are familiar with it:
I want to create ScriptableObjects (the class itself and the asset) with one button click.
So I have a base class like:
public abstract class State : ScriptableObject{}
As you can see it's going to be a StateMachine. Now I want kind of a window with a text field where I enter a name for a state and it creates a new class like:
public class Idle : State{}
And after that it creates the asset itself via:
AssetDatabase.CreateAsset<Idle>("/path/to/somewhere");
edit nearly working code
using System;
using System.IO;
using System.Linq;
using System.Text;
using AI.StateMachine.BaseClasses;
using UnityEditor;
using UnityEngine;
namespace AI.StateMachine.Editor
{
public class ActionWizard : EditorWindow
{
[MenuItem("Tools/AI/StateMachine/ActionWizard")]
static void Init()
{
// Get existing open window or if none, make a new one:
ActionWizard window = (ActionWizard) EditorWindow.GetWindow(typeof(ActionWizard));
window.Show();
}
private string actionName;
void OnGUI()
{
actionName = GUILayout.TextField(actionName);
if (GUILayout.Button("Create Action"))
{
//Get base class content
string baseClassContent =
File.ReadAllText($"{Application.dataPath}/Scripts/AI/StateMachine/BaseClasses/SM_Action.cs");
//refactor base class content to create derived class content
string newClassContent = baseClassContent
.Replace("SM_Action", actionName)
.Replace("ScriptableObject", "SM_Action")
.Replace("abstract ", "")
.Replace("namespace AI.StateMachine.BaseClasses", "namespace AI.StateMachine.Assets.Actions.Scripts")
.Replace("//", "");
//create derived class
using (FileStream fs = File.Create($"{Application.dataPath}/Scripts/AI/StateMachine/Assets/Actions/Scripts/{actionName}.cs"))
{
byte[] info = new UTF8Encoding(true).GetBytes(newClassContent);
fs.Write(info, 0, info.Length);
}
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
//get type of created class
var allTypes = AppDomain.CurrentDomain.GetAssemblies().SelectMany(assembly => assembly.GetTypes()).ToList();
var inputType = allTypes.Find(type => typeof(SM_Action).IsAssignableFrom(type) && type.Name == actionName);
//null....
//if type was created before it works and finds the desired type
Debug.Log(inputType);
//create scriptable object instance of created type
// var so = ScriptableObject.CreateInstance(inputType);
//save scriptable object asset
// AssetDatabase.CreateAsset(so,"Assets/Scripts/AI/StateMachine/Actions/"+actionName + ".asset");
// AssetDatabase.Refresh();
}
}
}
}

You can create type from string using:
Type.GetType(string typeAssemblyQualifiedName);
But you need to use Type.assemblyQualifiedName for that so "Idle" may not work if you use namespaces or assemblies, other option would be to get all types in current domains
var allTypes = AppDomain.CurrentDomain.GetAssemblies().SelectMany(assembly => assembly.GetTypes());
and find your desired type on that list somehow
var inputType = allTypes.Find(type => typeof(State).IsAssignableFrom(type) && type.Name == inputName);
then we create scriptable object instance using that type
var so = ScriptableObject.CreateInstance(inputType);

Related

Once again: JsonSerializationException: Unable to find a constructor to use for type "custom class"

I know this quesiton has already come up a lot, but I was looking through the answers and could not really find one that fits me, therefore I am asking you for help:
I have a custom class [Loadout]:
public class Loadout // used to save a players loadout on the server
{
public string name;
public float loadoutID;
public SO_Admiral admiral;
public FM_ShipSave[] shipsInTheLoadout;
public bool selectedLoadout;
public Loadout(string _name, FM_ShipSave[] _ShipSavesArray)
{
name = _name; // later this must come from an input-field
loadoutID = Random.Range(0f, 100000000f);
shipsInTheLoadout = _ShipSavesArray;
}
public Loadout(string _name)
{
name = _name;
shipsInTheLoadout = new FM_ShipSave[0];
}
}
it also includes other custom classes like:
public class FM_ShipSave
{
// this class saves ID and position in the array of the ship
public int shipID;
public int tileX;
public int tileZ;
public FM_ShipSave(UnitScript _unit)
{
shipID = _unit.SO_ofThis.getPositioninAllUnitsArray();
tileX = _unit.getTileX;
tileZ = _unit.getTileZ;
}
}
that I serialize into a .Json-file to send it to a server and store it there. The serialization part works perfectly:
var request = new UpdateUserDataRequest // after what is supposed to happen with the current loadout is determined we upload it to the server
{
Data = new Dictionary<string, string> // create a new dicitonary
{
{"Loadouts", JsonConvert.SerializeObject(playerLoadouts)} // convert the List<Loadout> playerLoadouts into a .json file
}
};
PlayFabClientAPI.UpdateUserData(request, onDataSend, OnError); // then send it to the server, to be stored
Link to picture of Json-editor
but as soon as I try to deserialize it:
List<Loadout> playerLoadouts = JsonConvert.DeserializeObject<List<Loadout>>(_result.Data["Loadouts"].Value); // turn the jsonback into a List<Loadout>
I am getting the following error:
Blockquote
JsonSerializationException: Unable to find a constructor to use for type Loadout. A class should either have a default constructor, one constructor with arguments or a constructor marked with the JsonConstructor attribute. Path '[0].name', line 1, position 9.
I have also tried it with a different simpler class and there everything works fine:
public class JsonTest
{
public string name;
public JsonTest(string _name)
{
name = _name;
}
}
Send:
// to test serialization
JsonTest _test = new JsonTest("test");
var request2 = new UpdateUserDataRequest // after what is supposed to happen with the current loadout is determined we upload it to the server
{
Data = new Dictionary<string, string> // create a new dicitonary
{
{"test", JsonConvert.SerializeObject(_test)} // convert the List<Loadout> playerLoadouts into a .json file
}
};
Retrieve:
public void testDeserialization(GetUserDataResult _result)
{
JsonTest _retrievedTest = JsonConvert.DeserializeObject<JsonTest>(_result.Data["test"].Value); // turn the jsonback into a List<Loadout>
Debug.Log("retrieved test deserialization name: " + _retrievedTest.name);
}
I therefore conclude that it has something to do with my custom classes, but I don't know how to fix it. Should I have to write a custom deserializer? Or is there maybe a different solution?
I was following this tutorial to create and upload json-files:
Link to tutorial on youtube
I am coding for unity on a mac using visual studio.
Thank you for your help.
The exception message is self explaining. You don't have a default constructor for the class Loadout. So in your Loadout class you need to add
Loadout() {}

access plugin instance properties

i want to use plugins to extend my c# wpf application. i've made a simple interface, then a plugin dll, then a test class to load the plugin. the plugin load properly and i can get a list of its properties.
the inteface:
public interface IStrat
{
string Name { get; }
void Init();
void Close(int dat);
}
the plugin:
public class Class1 : IStrat
{
public string info;
[Input("Info")]
public string Info
{
get
{
return info;
}
set
{
info = value;
}
}
public string Name
{
get { return "Test Strategy 1"; }
}
public void Init()
{
}
public void Close(int dat)
{
}
}
the test class:
class test
{
public void getPlugins()
{
Assembly myDll = Assembly.LoadFrom(Class1.dll);
var plugIn = myDll.GetTypes();
List<string> temp = new List<string>();
//gets the properties with "input" attribute, it returns the Info property fine
var props = item.GetProperties().Where(prop => Attribute.IsDefined(prop, typeof(Input)));
foreach (var prop in props)
{
temp.Add(prop.Name + " (" + prop.PropertyType.Name + ")");// returns Info (string)
}
stratFields.Add(item.Name, temp);// stratFields is a dictionary that keeps the name of the plugin as key and a list of properties names as value
}
public void create()
{
//create an instance of my plugin
Type t = plugIn[0];
var myPlugin = (IStrat)Activator.CreateInstance(t);
myPlugin.Init(); // this works, i can access the methods
myPlugin.Info = "test"; //this doesn't work
}
}
i want to access the "Info" property to get/set it for that specific instance. when i use the getproperties() method it finds it, so there must be a way to use it.
different plugins have different number and type of properties.
Since Info property is not part of the interface you have to use reflection (Set object property using reflection) or dynamic:
myPlugin.Init(); // this works because IStrat has Init method
dynamic plugin = myPlugin;
plugin.Info = "test"; // this works because `Info` is public property and
// dynamic will perform late (run-time) binding.
Better approach would be add all necessary methods to the interface.

Accessing values from the string name of a class at runtime

In my source code for a C# class, I want to use the values of parameters defined within some other class. I want to do it without "hardwiring" the other class name into the source code (e.g., without writing something like "class2.variable").
Rather, I want to pass the name of that other class as a character string at runtime.
I am using C# within Unity. So I want to set the name of the other class as a public string within the Inspector of Unity.
For example, consider these two separate scripts:
using UnityEngine ;
public class ScriptA : ScriptableObject {public static int fred = 5 ; }
and
using System;
using System.Reflection;
using UnityEngine;
public class ScriptB : MonoBehaviour {
object item;
private static string instance;
void Start() {
instance = "ScriptA";
item = ScriptableObject.CreateInstance(Type.GetType(instance));
Type myType = item.GetType();
foreach (FieldInfo info in myType.GetFields())
{
string infoName = info.Name; //gets the name of the field
Debug.Log (" info = " + infoName);
}
}
}
ScriptB works OK ; it accesses ScriptA just from the string "instance", as evidenced by the fact that
the name "fred" to appears in the console.
But how do I access the value of "fred" ; how do I make the number "5" appear on the console?
I have been trying for two days. I have searched extensively for an answer.
Can somebody please help?
FieldInfo has a GetValue method :
public abstract object GetValue(
object obj
)
Try:
Type myType = item.GetType();
foreach (FieldInfo info in myType.GetFields())
{
string infoName = info.Name; //gets the name of the property
Console.WriteLine(" Field Name = " + infoName +"and value = "+ info.GetValue(null));
}

Using Roslyn to create partial classes - one compiled at runtime

I have a class (HelloWorld.cs):
public partial class HelloWorld
{
public void SayHello()
{
var message = "Hello, World!";
var length = message.Length;
Console.WriteLine("{1} {0}", message, length);
}
}
The above class the property BuildAction = Compile.
I have another class in a separate file (HelloWorldExtend.cs):
public partial class HelloWorld
{
public void SayHelloExtend()
{
var message = "Hello, World Extended!";
var length = message.Length;
Console.WriteLine("{1} {0}", message, length);
}
}
But the properties of the class are: BuildAction = None and Copy to output directory = Copy if newer
Now the main method:
Its using Roslyn.
static void Main(string[] args)
{
var code = File.ReadAllText("HelloWorldExtend.cs");
var tree = SyntaxFactory.ParseSyntaxTree(code);
var compilation = CreateCompilation(tree);
var model = compilation.GetSemanticModel(tree);
ExecuteCode(compilation);
Console.ReadLine();
}
private static void ExecuteCode(CSharpCompilation compilation)
{
using (var stream = new MemoryStream())
{
compilation.Emit(stream);
var assembly = Assembly.Load(stream.GetBuffer());
var type = assembly.GetType("HelloWorld");
var greeter = Activator.CreateInstance(type);
var methodextend = type.GetMethod("SayHelloExtend");
methodextend.Invoke(HelloWorld, null);
//Works perfect
var method = type.GetMethod("SayHello");
method.Invoke(greeter, null);
//method is returned null and gives an error : {"Object reference
not set to an instance of an object."}
}
}
IS it possible to use roslyn to give the same effect as a regular partial class to an existing class where one class is compiled during build and another is compiled at runtime in the same assembly.
Short answer: No.
The original assembly had already been compiled. The class definition for HelloWorld is already converted to IL and at compile time there was no additional source file to make up the other parts of the partial class.
You can create a new assembly containing its own version of HelloWorld by supplying it both parts of the partial file as source.
However
It looks like you may be able to simply extend the original class and optionally make the currently compiled class an abstract class.
public abstract class HelloWorldBase
{
public void SayHello()
{
var message = "Hello, World!";
var length = message.Length;
Console.WriteLine("{1} {0}", message, length);
}
}
Set above class the property BuildAction = Compile.
public class HelloWorld : HelloWorldBase
{
public void SayHelloExtend()
{
var message = "Hello, World Extended!";
var length = message.Length;
Console.WriteLine("{1} {0}", message, length);
}
}
Make sure that as part of your compilation, you reference the assembly containing HelloWorldBase before actually compiling the sources:
compilation.AddReferences(new MetadataFileReference(typeof(HelloWorldBase).Assembly.location));
That should work.
No, as indicated in this answer, partial classes are a "purely language feature". On the CLR level, there is only one class. Since Roslyn will eventually just emit an assembly, you cannot "amend" your class like that.
In fact, there is no such think as partial classes. The real language feature is partial class definitions.
As you can see on the documentation:
It is possible to split the definition of a class or a struct, an interface or a method over two or more source files. Each source file contains a section of the type or method definition, and all parts are combined when the application is compiled.

C#: Dynamically instantiate different classes in the same statement?

Here is a simplified version of what I'm trying to do:
Without having multiple if..else clauses and switch blocks, can I mimic the behavior of Javascript's eval() shudder to instantiate a class in C#?
// Determine report orientation -- Portrait or Landscape
// There are 2 differently styled reports (beyond paper orientation)
string reportType = "Portrait";
GenericReport report;
report = new eval(reportType + "Report()"); // Resolves to PortraitReport()
The need stems from the fact that I have 6 types of Crystal Reports (that do the same thing, but look drastically different) for 50 states. There are 3 styles each, rather than entertain the notion of a giant switch block with nested if..else statements determining which of 900 reports to use, I was hoping for an eval-like solution.
You could use Activator.CreateInstance("myAssembly", "PortrainReport");. Although the more readable way would be to create a Portrait Factory, which would create the correct type for you.
As people specified above, you can use Activator class to create an instance of the class by its text name.
But, there is one more option.
When you told about using eval like function in c# i assumed, you not only want to create an instance of the class by its text name, but also fill it with properties from the same string.
For this purpose you need to use deserialization.
Deserialization converts string like representation of the class into its instance and restoring all its properties that was specified in the string.
Xml serialization. Its using XML file for converting into instance.
Here is small example:
public class Report1
{
public string Orientation {get;set;}
public string ReportParameter1 {get;set;}
public string ReportParameter2 {get;set;}
}
Above is the class that you want to instantiate and fill with parameters by string line.
Below is XML that can do that:
<?xml version="1.0"?>
<Report1>
<Orientation>Landscape</Orientation>
<ReportParameter1>Page1</ReportParameter1>
<ReportParameter2>Colorado</ReportParameter2>
</Report1>
To create an instance from the file use System.Xml.Serialization.XmlSerializer :
string xml = #"<?xml version=""1.0""?>
<Report1>
<Orientation>Landscape</Orientation>
<ReportParameter1>Page1</ReportParameter1>
<ReportParameter2>Colorado</ReportParameter2>
</Report1>";
///Create stream for serializer and put there our xml
MemoryStream str = new MemoryStream(ASCIIEncoding.ASCII.GetBytes(xml));
///Getting type that we are expecting. We are doing it by passing proper namespace and class name string
Type expectingType = Assembly.GetExecutingAssembly().GetType("ConsoleApplication1.Report1");
XmlSerializer ser = new XmlSerializer(expectingType);
///Deserializing the xml into the object
object obj = ser.Deserialize(str);
///Now we have our report instance initialized
Report1 report = obj as Report1;
In this way you can prepare appropriate xml as string concatenation. That xml will contain all parameters for your report.
Then, you can convert it into the proper type.
Look at the Activator create instance method
All the classes will need to adhere to an interface. Then make an Generic Method which will be your eval and requires that interface. Here is an example of this (call the Usage static to see it in action):
public interface IOperation
{
string OutputDirection { get; set; }
};
public class MyOperation: IOperation
{
public string OutputDirection { get; set; }
}
public static class EvalExample
{
public static T Eval<T>( string direction ) where T : IOperation
{
T target = (T) Activator.CreateInstance( typeof( T ) );
target.OutputDirection = direction;
return target;
}
// Example only
public static void Usage()
{
MyOperation mv = Eval<MyOperation>( "Horizontal" );
Console.WriteLine( mv.OutputDirection ); // Horizontal
}
}
Using the factory pattern, and reflection (as explained in this blog post), you would get:
static void Main(string[] args)
{
ReportFactory<Report> factory = new ReportFactory<Report>();
Report r1 = factory.CreateObject("LandscapeReport");
Report r2 = factory.CreateObject("PortraitReport");
Console.WriteLine(r1.WhoAmI());
Console.WriteLine(r2.WhoAmI());
}
Which would output "Landscape" and "Portrait", respectivley.
Of course, for the plumbing, you need an interface that all your reports are based off of (which I assume you already have).
For this example:
public interface Report
{
string WhoAmI();
}
And the two implemenations:
public class PortraitReport : Report
{
public string WhoAmI()
{
return "Portrait";
}
}
public class LandscapeReport : Report
{
public string WhoAmI()
{
return "Landscape";
}
}
The secret is in the ReportFactory, which uses Reflection to see what other classes are based on Report, and automatically register them for use, which I think is pretty cool:
public class ReportFactory<Report>
{
private Dictionary<string, Type> reportMap = new Dictionary<string, Type>();
public ReportFactory()
{
Type[] reportTypes = Assembly.GetAssembly(typeof(Report)).GetTypes();
foreach (Type reportType in reportTypes)
{
if (!typeof(Report).IsAssignableFrom(reportType) || reportType == typeof(Report))
{
// reportType is not derived from Report
continue;
}
reportMap.Add(reportType.Name, reportType);
}
}
public Report CreateObject(string ReportName, params object[] args)
{
return (Report)Activator.CreateInstance(reportMap[ReportName], args);
}
}
So now all you have to do is just add any new implementations of Report in your assembly, and they will be available to the factory with no extra coding or changing other code files.

Categories