I am trying to use Generics with Reflection in c# to build a method that can handle multiple classes. I use a 3rd-party DLL that has a bunch of classes and on those classes, there is a method I call. They all return different return types, but I do the same processing once I get the object back (in my example below, that would be AreaA and AreaB).
Basically, I want to develop a method that takes in the class name and the expected return type as Generic variables and then calls the correct method (methodName) which is supplied as a parameter to this method.
The program below compiles fine and runs without error, but the issue is the expected type of the 'area' variable. In the below statements, the first line is type casted to (TArea), and if I hover over it In Visual Studio, the intellisense shows the property 'name', but typing area.name doesn't give me the value. I have to type ((AreaA)area).name.
Problem is the type 'AreaA' could be another type at run-time. In this example, 'AreaB' so I can hard-code a cast.
How can I accomplish casting the 'area' variable to the appropriate type allowing me to see the public methods/properties of the 3rd-parties class?
NOTE: In my example, eveything is in the same class, but in reality the definitions for ServiceA, ServiceB, AreaA and AreaB will be in a 3rd party DLL.
As always, thanks in advance!
Fig 1 - 'area' variable can only get 'name' property if casted to 'AreaA'
area = (TArea)dfpMethod.Invoke(instance, new object[] { "Area123" });
AreaA areaa = (AreaA)dfpMethod.Invoke(instance, new object[] { "Area123" });
Fig 2. - Complete Program
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;
using System.IO;
namespace ReflectionExample
{
class Sample
{
class ServiceA
{
public int size {get; set;}
public string name {get; set;}
public ServiceA()
{
name = "TestA";
size = 100;
}
public AreaA doWork(string name)
{
return new AreaA(name);
}
}
class AreaA
{
public string name { get; set;}
public AreaA(string name)
{
this.name = name;
}
public AreaA()
{
}
}
class ServiceB
{
public int size { get; set; }
public string name { get; set; }
public ServiceB()
{
name = "TestB";
size = 50;
}
public AreaB doWork(string name)
{
return new AreaB(name);
}
}
class AreaB
{
public string name { get; set; }
public AreaB(string name)
{
this.name = name;
}
public AreaB()
{
}
}
static void Main(string[] args)
{
runService<ServiceA, AreaA>("doWork");
}
private static void runService<TService, TArea>(string methodName)
where TService : class, new()
where TArea : class, new()
{
//Compile time processing
Type areaType = typeof(TArea);
Type serviceType = typeof(TService);
//Print the full assembly name and qualified assembly name
Console.WriteLine("AreaType--Full assembly name:\t {0}.", areaType.Assembly.FullName.ToString()); // Print the full assembly name.
Console.WriteLine("AreaType--Qualified assembly name:\t {0}.", areaType.AssemblyQualifiedName.ToString()); // Print the qualified assembly name.
Console.WriteLine("ServiceType--Full assembly name:\t {0}.", serviceType.Assembly.FullName.ToString()); // Print the full assembly name.
Console.WriteLine("ServiceType--Qualified assembly name:\t {0}.", serviceType.AssemblyQualifiedName.ToString()); // Print the qualified assembly name.
//This is done because in my code, the assembly doesn't reside in the executiy assembly, it is only setup as a reference
var assembly = Assembly.Load(serviceType.Assembly.FullName);
//Initialize the generic area
TArea area = default(TArea);
//Get an instance of the service so I can invoke the method later on
var instance = Activator.CreateInstance(serviceType);
//Get the methodInfo for the methodName supplied to the runService method
MethodInfo dfpMethod = serviceType.GetMethod(methodName, BindingFlags.Public | BindingFlags.Instance);
//area is type casted to (TArea), the intellisense shows the property 'name', but typing area.name doesn't give me the value
//I have to type ((AreaA)area).name. Problem is the type 'AreaA' could be another type. In this example, 'AreaB'
area = (TArea)dfpMethod.Invoke(instance, new object[] { "Area123" });
AreaA areaa = (AreaA)dfpMethod.Invoke(instance, new object[] { "Area123" });
Console.WriteLine();
}
}
}
The source of your error is that you're casting all of your return values to type TArea with the statement:
TArea area = (TArea)dfpMethod.Invoke(instance, new object[] { "Area123" });
Per your generic specification, the only thing promised to you by the type TArea is that its a class. Therefore, TArea doesn't give you access to anything but members of the 'object' type.
Instead, do away with the TArea generic argument in favor of using the 'dynamic' keyword:
var area = (dynamic)dfpMethod.Invoke(instance, new object[] { "Area123" });
return area.name; // no error
Note, this is only relevant if the actual types AreaA and AreaB are defined in third party libraries (as you say) and you can't modify them. If you can't modify the classes, you can't introduce an interface (which is what you really need here). If you can't introduce an interface, but all the types expose identical signatures, you can assume the existence of the relevant members using the dynamic type.
If you need to do a lot of work with AreaA/AreaB and you don't want the performance overhead of all the dynamic operations, define your own generalized class that exposes all the signatures you need:
public class MyGenericArea
{
public MyGenericArea(string name)
{
this.Name = name;
}
public string Name {get; set;}
}
Then populate the class using the dynamic casting and return that class type instead:
var area = (dynamic)dfpMethod.Invoke(instance, new object[] { "Area123" });
return new MyGenericArea(area.name);
I think the problem you will have here is mixing dynamically loaded types (Assembly.Load()) with types that are referenced directly from your project and you can see in intellisense, i.e. AreaA.
If you are dynamically loading entire assemblies using reflection, intellisense will do nothing to help you view the class members, as that information needs to be known at compile time, and by definition you are loading assemblies at runtime.
If you just want to view a list of all of the public properties available to your type, then you can use this:
var areaProperties = area.GetType().GetProperties();
But again, this is all done at run time so it wont help you when writing code.
You can dynamically read the value of the "name" property using:
var nameValue = area.GetType().GetProperty("name").GetValue(area);
Essentially, if you want intellisense, reference the dll's directly from your Visual Studio project rather than using Assembly.Load().
Hope that helps.
Related
I am working on an application that stores data in the ConfigurationManager.AppSettings file, and I am wanting to implement it in a different way than how I do right now. Currently, I have an interface (see below) that each class with saveable traits needs to implement, then call the static save methods from my Config class (example below). I don't like the coupling between my Config class and the class with the saveable data, so my ideal would be to have an attribute that indicates a property should be saved. Then, instead of calling the SaveData or LoadData functions in my manager class, I would call a function that sets/saves all the attributed properties. This seems similar to how [Serializeable] works in default C#, so I imagine it's possible somehow. However, most of my searches have been fruitless. Any ideas on how to implement something like this?
Interface
Example
Reflection is what you're looking for.
Reflection provides objects (of type Type) that describe assemblies, modules, and types. You can use reflection to dynamically create an instance of a type, bind the type to an existing object, or get the type from an existing object and invoke its methods or access its fields and properties. If you are using attributes in your code, reflection enables you to access them.
Assuming that you're only interested in properties, you can use typeof or GetType to get an instance of System.Type. You can then call GetProperties to get an IEnumerable<PropertyInfo>. PropertyInfo has an Attributes property that you can use to retrieve the attributes for that property. You can also use an instance of PropertyInfo to retrieve the value of the property.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
[AttributeUsage(AttributeTargets.Property)]
public class MyAttribute : Attribute
{
}
public class Foo
{
[My]
public string Bar { get; set; }
public string Baz { get; set; }
[My]
public string Id { get; set; }
}
public static class Utilities
{
public static IEnumerable<PropertyInfo> GetPropertiesWithMyAttribute(object obj)
{
return obj.GetType()
.GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Where(pi => pi.CustomAttributes.Any(ca => ca.AttributeType == typeof(MyAttribute)));
}
}
class Program
{
static void Main(string[] args)
{
var foo = new Foo()
{
Bar = "Bar_Value",
Baz = "Baz_Value",
Id = "Id_Value"
};
foreach (var pi in Utilities.GetPropertiesWithMyAttribute(foo))
{
Console.WriteLine($"{pi.Name}: {pi.GetMethod.Invoke(foo, null).ToString()}");
}
foreach (var pi in Utilities.GetPropertiesWithMyAttribute(foo))
{
pi.SetMethod.Invoke(foo, new object[] { $"{pi.Name}_Value_Reflection" });
}
Console.WriteLine(foo.Bar);
Console.WriteLine(foo.Baz);
Console.WriteLine(foo.Id);
Console.ReadKey();
}
}
Of course, this example only string properties. You're going to have to figure out some way to deal with properties that aren't strings; for example you haven an ObservableCollection in your example.
Please forgive me if the title is not worded correctly.
I am retrieving data from database tables for various devices and building a list. Different devices could have the same properties and will definitely have some properties that differ, So I am using a Factory Pattern to create whichever is needed at run time.
Factory class:
public interface IImportModel
{
IList CreateImportList(SqlDataReader reader);
}
And Concrete class:
public class Device1ImportModel : IImportModel
{
public string SerialNumber { get; set; }
public string PartA { get; set; }
public IList CreateImportList(SqlDataReader reader)
{
Device1ImportModel linkedItem = new Device1ImportModel();
List<Device1ImportModel> importList = new List<Device1ImportModel>();
while (reader.Read())
{
linkedItem = new Device1ImportModel();
linkedItem.SerialNumber = reader["SerialNo"].ToString();
linkedItem.PartA = reader["PartA"].ToString();
importList.Add(linkedItem);
}
return importList;
}
}
I create the device from the factory:
importModel = ImportModelFactory.CreateImportModel("Device1");
Now when I want to iterate over the importModel, I receive a compile time error on the line where I attempt to access item.SerialNumber
foreach (var item in importList)
{
string number = item.SerialNumber;
}
The error:
'object' does not contain a definition for 'SerialNumber' and no extension method 'SerialNumber' accepting a first argument of type 'object' could be found.
If I place a breakpoint and hover over item variable, I can see the properties and value.
I tried using dynamic instead of var, but then later in my code I can no longer use Linq or Lambda queries.
How can I access the values?
Can I convert the IList to List perhaps using Reflection?
Edit 1
Added Code for CreateImportModel:
static public IImportModel CreateImportModel(DeviceType device)
{
switch (device)
{
case DeviceType.Device1:
return new Device1ImportModel();
case DeviceType.Device2:
return new DeviceImportModel();
default:
return null;
}
}
If you cannot change your method's signature, you can use:
foreach (var item in importList.Cast<Device1ImportModel>())
{
string number = item.SerialNumber;
}
This will throw an exception, however, if there will be an object in the importList collection that is not a Device1ImportModel or its derived class.
If you're not sure that all objects in the list are of that type and want to avoid exceptions, use this apporach:
foreach (var item in importList.OfType<Device1ImportModel>())
{
string number = item.SerialNumber;
}
Change IList to List<Device1ImportModel> (or IList<Device1ImportModel> or IReadOnlyList<Device1ImportModel>).
public List<Device1ImportModel> CreateImportList(SqlDataReader reader)
IList is an older interface (pre-generics) and thus if you use IList (rather than IList<Device1ImportModel) then the compiler / runtime has no notion of the Type of your data (i.e. it treats it as object), thus:
'object' does not contain a definition for 'SerialNumber' and no
extension method 'SerialNumber' accepting a first argument of type
'object' could be found.
You may also need to change the interface to:
public interface IImportModel<T>
{
List<T> CreateImportList(SqlDataReader reader);
}
and the class to:
public class Device1ImportModel : IImportModel<Device1ImportModel>
{
public string SerialNumber { get; set; }
public string PartA { get; set; }
public List<Device1ImportModel> CreateImportList(SqlDataReader reader)
{
You likely also want to change CreateImportModel so instead of calling it like:
ImportModelFactory.CreateImportModel("Device1");
you instead call it like:
ImportModelFactory.CreateImportModel<Device1ImportModel>();
so that a concrete Device1ImportModel is returned (and thus SerialNumber is accessible).
I ended up just using a separate class to hold all the properties, whether each other concrete class uses them or not.
Its not ideal but works.
I would have preferred relying on each concrete class to be responsible for it's own properties though.
I have this class:
namespace Ns1.Ns2.Domain
{
public class Process
{
private IIndex IndexBuilderConcr { get; set; }
public Processo(String processType) {
IndexBuilderConcr = new UnityContainer().RegisterType<IIndex, *>().Resolve<IIndex>();
}
}
}
Here I have a constructor that takes a String. This string represent a type that should replace the * sign at line 8.
I have googled araound but with no luck.
What you'll need to do is get the type in the way James S suggests, but you'll need to pass that value into the method in a slightly different way as calling Method<resolvedProcessType> is invalid:
var type = Type.GetType("Some.Name.Space." + processType);
var methodInfo = typeof(UnityContainer).GetMethod("RegisterType");
// this method's argument is params Type[] so just keep adding commas
// for each <,,,>
var method = methodInfo.MakeGenericMethod(IIndex, type);
// we supply null as the second argument because the method has no parameters
unityContainer = (UnityContainer)method.Invoke(unityContainer, null);
unityContainer.Resolve<IIndex>();
The dav_i answer is pretty correct, indeed, since I'm using Unity the best way to achieve this is named mapping:
namespace Ns1.Ns2.Domain
{
public class Process
{
private IIndex IndexBuilderConcr { get; set; }
public Processo(String processType) {
IndexBuilderConcr = new UnityContainer().Resolve<IIndex>(processType);
}
}
}
and then inside the App.config:
<container>
<register type="IIndex" mapTo="IndexAImpl" name="A"/>
<register type="IIndex" mapTo="IndexBImpl" name="B"/>
</container>
where name attributes is the processType string.
The method for this is the static Type.GetType(fullyQualifiedTypeNameString) You need to know the namespace for this to be possible, but the assembly name is inferred if it is in the currently executing assembly.
So if the namespace is known, but not passed in you could do
string fullyQualifiedTypeName = "MyNamespace." + processType;
Type resolvedProcessType = Type.GetType(fullyQualifiedTypeName);
However you can't just pass this Type object as a typeparameter, because that's not how generics work. Generics require the type to be known at compile time - so passing a Type object as a Typeparameter is not a valid way of saying that is the required Type.
To get round this reflection can be used instead. There is another answer by dav_i which demonstrates this.
This is just an example... My "real life" example is a much more complex. Take this rough example:
public struct _User
{
public string FirstName;
public string MiddleName;
public string LastName;
}
I can set the names then pass this by reference to a function.
However, I want to pass a structure of data by reference into dynamically compiled code.
public void RunScript(string ScriptName, ref _User stUser)
{
private Microsoft.CSharp.CSharpCodeProvider _compiler;
public CompilerResults _compileResults;
private static object _compiledAssembly;
MI.Invoke(_compiledAssembly, new object[]{
ref stUser }); // can't do a reference here...
MI = _compiledAssembly.GetType().GetMethod(ScriptName);
}
Yes, I do have a "copy" of that structure in the dynamic code...
Is it possible to pass this structure by reference into the dynamic code?
To start with, I think you want the two statements in the opposite order - there's no much point in setting MI after you've invoked it...
But then, you should just be able to set the value in an object[] which you retain a reference to - after the call, the value in the array will have changed. So you want:
object[] args = { stUser; }
MI.Invoke(_compiledAssembly, args);
stUser = (_User) args[0]; // Or whatever you want to do with it
(Do you really need it to be a struct at all? And with public fields, no less?)
I'm a bit perturbed by this part, by the way:
Yes, I do have a "copy" of that structure in the dynamic code...
If you mean there's another type defined in your dynamically-generated assembly, that's a problem. The two types will not be the same, even if they've got the same namespace-qualified name. You should make your dynamically-generated assembly refer to the assembly containing the type. You really don't want two types with the same name in different assemblies - it will make debugging a real pain...
EDIT: Short but complete example:
using System;
using System.Reflection;
public struct MutableStruct
{
public int x;
}
class Test
{
public static void ChangeByRef(ref MutableStruct foo)
{
foo = new MutableStruct { x = 10 };
}
static void Main()
{
var args = new object[] { new MutableStruct() };
var method = typeof(Test).GetMethod("ChangeByRef");
method.Invoke(null, args);
var changed = (MutableStruct) args[0];
Console.WriteLine(changed.x); // Prints 10
}
}
I have a class that used to have a string return type. Now I find I need to return more than a string. I was thinking to return something like below:
public string Test()
{
return ( new { ID = 5, Name= "Dave" } );
}
Is this even possible and if so then what would be the return type? I know it's not string ..
As others have said, the best thing to do here is to make a nominal type. I would suggest that the nominal type have the same characteristics as an anonymous type; that is, you should consider making the type immutable and consider making it exhibit value equality.
It is possible to return an anonymous type as object and then use the instance returned elsewhere using a variety of sneaky techniques. You can cast the object to "dynamic" (in C# 4) and then use the properties of the anonymous type, but this is slow and lacks compile-time type checking.
You can also use the "cast by example" trick, which does get you compile-time type checking. However, that trick only works when the anonymous source object and the anonymous example object come from the same assembly.
static T CastByExample<T>(object source, T example) where T : class
{
return source as T;
}
static object ReturnsAnonymous() { return new { X = 123 }; }
static void DoIt()
{
object obj = ReturnsAnonymous();
var example = new { X = 0 };
var anon = CastByExample(obj, example);
Console.WriteLine(anon.X); // 123
}
See how sneaky that is? We use method type inference and local variable type inference to tell the compiler "these two things are the same type". This lets you export an anonymous type as object and cast it back to anonymous type.
But you probably should not do this; if you're resorting to such sneaky tricks then you should simply be defining a nominal type in the first place. Also, like I said, the trick only works if the example and the source objects were created in code in the same assembly; two "identical" anonymous types in two different assemblies do not unify to be the same type.
The object that you return does have a class, but it's anonymous so you can't specify it in the code. You just have to return it as an object reference:
public object Test() {
return new { ID = 5, Name= "Dave" };
}
Note that the anonymous type is unknown outside the scope of the method, so reflection is the only way to access its properties.
If you want to be able to use the returned object conveniently, you should declare a class:
public class TestResult
{
public int ID { get; set; }
public string Name { get; set; }
}
public TestResult Test() {
return new TestResult() { ID = 5, Name= "Dave" };
}
Another alternative is to use an existing class, if it fits your purpose. A KeyValuePair is close to what you use, but then the properties will of course be named Key and Value instead of ID and Name:
public KeyValuePair<int, string> Test() {
return new KeyValuePair<int, string>(5, "Dave");
}
This isn't possible as the anonymous class is only valid within the current context. If you need to return an object then you'll need to create a real class.
I'm assuming you left string as the return type by accident.
Anonymous type are class type that are derived directly from object.
You can return it from method as object as return type.
Have a look at this.
No, it's not possible. Your options are:
Define a real class for the return value,
Use System.Tuple, or
Use out parameters (probably the least good option).
You can make a struct (or class) for this.
public struct IdAndName
{
public int Id;
public string Name;
public IdAndName(int id, string name)
{
ID = id;
Name = name;
}
}
You could also use a Tuple<T1, T2>, (but that's not recommended as the properties aren't named.
class NewString
{
public int ID { get; set; }
public string Name { get; set; }
}
public NewString Test()
{
return ( new NewString() { ID = 5, Name = "Dave" } );
}
:)