When trying to cast a class which defines a custom generic to the subclass I get an error. What did I wrong?
using System.Collections.Generic;
interface IStorageComponent { }
abstract class CollectionManager<T> where T : IStorageComponent { }
class Bar : IStorageComponent { }
class Foo : CollectionManager<Bar> { }
class Storage
{
static int Main(string[] args)
{
List<CollectionManager<IStorageComponent>> manager = new List<CollectionManager<IStorageComponent>>
{
new Foo() // <-- cannot convert from 'Foo' to 'CollectionManager<IStorageComponent>'
};
return 0;
}
}
Related
In this example, I want to patch PatchTarget.QSingleton\<T\>.get_Instance().
How to get it done with Harmony or MonoMod?
Harmony:
"Unhandled exception. System.NotSupportedException: Specified method
is not supported."
MonoMod:
"Unhandled exception. System.ArgumentException: The given generic
instantiation was invalid."
Code snippet: (runnable with dotnetfiddle.net)
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using HarmonyLib;
namespace PatchTarget {
public abstract class QSingleton<T> where T : QSingleton<T>, new() {
protected static T instance = null; protected QSingleton() { }
public static T Instance { get {
if (instance == null) {
instance = new T();
Console.Write($"{typeof(T).Name}.Instance: impl=QSingleton");
}
return instance;
} }
}
}
namespace Patch {
public class TypeHelper<T> where T : PatchTarget.QSingleton<T>, new() {
public static T InstanceHack() {
Console.Write($"{typeof(T).Name}.Instance: impl=InstanceHack");
return null;
}
}
public static class HarmonyPatch {
public static Harmony harmony = new Harmony("Try");
public static void init() {
var miOriginal = AccessTools.Property(typeof(PatchTarget.QSingleton<>), "Instance").GetMethod;
var miHack = AccessTools.Method(typeof(TypeHelper<>), "InstanceHack");
harmony.Patch(miOriginal, prefix: new HarmonyMethod(miHack));
}
}
public static class MonoModPatch {
public static MonoMod.RuntimeDetour.Detour sHook;
public static void init() {
var miOriginal = AccessTools.Property(typeof(PatchTarget.QSingleton<>), "Instance").GetMethod;
var miHack = AccessTools.Method(typeof(TypeHelper<>), "InstanceHack");
sHook = new MonoMod.RuntimeDetour.Detour(miOriginal, miHack);
}
}
}
class Program {
public static void Main() {
Patch.HarmonyPatch.init();
// Patch.MonoModPatch.init();
Console.WriteLine($"done");
}
}
After some trial and error, I got something working, but not the reason behind it.
Both Harmony and MonoMod.RuntimeDetour can hook with the typeof(QSingleton<SampleA>).GetMethod(), but not typeof(QSingleton<>).GetMethod().
Harmony output is unexpected.
Harmony attribute annotation doesn't seem to work.
Generating IL seems useless due to the potential lack of TypeSpec for generic.
Questions:
What is the difference between QSingleton<>.Instance and QSingleton<SampleA>.Instance in the sample?
I would guess that <>.Instance is MethodDef, while <SampleA>.Instance is TypeSpec.MemberRef.
Why does Harmony/MonoMod.RuntimeDetour need TypeSpec.MemberRef? For generating redirection stub?
Is it possible to fix the hook under Harmony?
Can Harmony/MonoMod generates "ldtoken <TypeSpec>" if TypeSpec already exists?
Can Harmony/MonoMod dynamically generates necessary TypeSpec for generics?
Code snippet: (runnable with dotnetfiddle.net)
using System;
using HarmonyLib;
namespace PatchTarget {
public abstract class QSingleton<T> where T : QSingleton<T>, new() {
protected static T instance = null; protected QSingleton() { }
public static T Instance { get {
if (instance == null) {
instance = new T();
Console.WriteLine($"{typeof(T).Name}.Instance: impl=QSingleton");
}
return instance;
} }
}
public class SampleA : QSingleton<SampleA> {
public SampleA() { Console.WriteLine("SampleA ctor"); }
}
public class SampleB : QSingleton<SampleB> {
public SampleB() { Console.WriteLine("SampleB ctor"); }
}
}
namespace Patch {
public class TypeHelper<T> where T : PatchTarget.QSingleton<T>, new() {
public static T InstanceHack() {
Console.WriteLine($"{typeof(T).Name}.Instance: impl=InstanceHack");
return null;
}
// For Harmony as Prefix, but attribute does not work.
public static bool InstanceHackPrefix(T __result) {
Console.WriteLine($"{typeof(T).Name}.Instance: impl=InstanceHack");
__result = null;
return false;
}
}
public static class HarmonyPatch {
public static Harmony harmony = new Harmony("Try");
public static void init() {
// Attribute does not work.
// Transpiler does not work because the lack of TypeSpec to setup generic parameters.
var miOriginal = AccessTools.Property(typeof(PatchTarget.QSingleton<PatchTarget.SampleB>), "Instance").GetMethod;
var miHack = AccessTools.Method(typeof(TypeHelper<PatchTarget.SampleB>), "InstanceHackPrefix");
harmony.Patch(miOriginal, prefix: new HarmonyMethod(miHack));
}
}
public static class MonoModPatch {
public static MonoMod.RuntimeDetour.Detour sHook;
public static void init() {
var miOriginal = AccessTools.Property(typeof(PatchTarget.QSingleton<PatchTarget.SampleB>), "Instance").GetMethod;
var miHack = AccessTools.Method(typeof(TypeHelper<PatchTarget.SampleB>), "InstanceHack");
sHook = new MonoMod.RuntimeDetour.Detour(miOriginal, miHack);
}
}
}
class Program {
public static void Main() {
_ = PatchTarget.SampleA.Instance;
// MonoMod works (replaces globally).
// Harmony hooks, but in an expected way (T becomes SampleB, not 1st generic type parameter).
// try { Patch.HarmonyPatch.init(); } catch (Exception e) { Console.WriteLine($"Harmony error: {e.ToString()}"); }
try { Patch.MonoModPatch.init(); } catch (Exception e) { Console.WriteLine($"MonoMod error: {e.ToString()}"); }
_ = PatchTarget.SampleB.Instance;
_ = PatchTarget.SampleA.Instance;
Console.WriteLine($"done");
}
}
MonoMod.RuntimeDetour Output:(Work as intended)
SampleA.Instance: impl=QSingleton
SampleB.Instance: impl=InstanceHack
SampleA.Instance: impl=InstanceHack
Harmony Output:(Broken <T>)
SampleA.Instance: impl=QSingleton
SampleB.Instance: impl=InstanceHack
SampleB.Instance: impl=InstanceHack
What it boils down to is that I'm trying to make a Generic and while the type shows up correctly at runtime, during compile time its still object, and so I cannot use any of the generic type's methods.
thanks to brainless coder on a previous question I'm able to move forward a bit
dotnetfiddle
using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static void Main()
{
var sample = new Baz<List<Foo>>();
sample.DoSomething();
}
public class Foo
{
}
public class Bar<T>
{
public void Boom()
{
}
}
public class Baz<T>
{
public void DoSomething(){
if (typeof(T).Name == "List`1")
{
var typeName = typeof(T).GetGenericArguments().Single().FullName;
var type = Type.GetType(typeName);
var genericRepoType = typeof(Bar<>);
var specificRepoType = genericRepoType.MakeGenericType(new Type[] { type });
var genericBar = Activator.CreateInstance(specificRepoType);
Console.WriteLine(genericBar.GetType().Name); // Shows Bar`1
// but at compile time its foo is still an object
genericBar.Boom();
//will error with 'object' does not contain a definition for Boom
}
}
}
}
This sounds like a very questionable design, but if you must, dynamic neatly solves your problem.
public static void Main() {
var sample = new Baz<List<Foo>>();
sample.DoSomething();
}
public class Foo { }
public class Bar<T> {
public void Boom() {
Console.WriteLine("I am booming");
}
}
public class Baz<T> {
public void DoSomething() {
var typeName = typeof(T).GetGenericArguments().Single().FullName;
var type = Type.GetType(typeName);
var genericRepoType = typeof(Bar<>);
var specificRepoType = genericRepoType.MakeGenericType(new Type[] { type });
dynamic genericBar = Activator.CreateInstance(specificRepoType);
Console.WriteLine(genericBar.GetType().Name);
genericBar.Boom();
}
}
https://dotnetfiddle.net/uPpfJa
Alternatively, you could declare an IBar interface.
public class Bar<T> : IBar {
public void Boom() {
Console.WriteLine("I am booming");
}
}
interface IBar {
void Boom();
}
...
var genericBar = (IBar)Activator.CreateInstance(specificRepoType);
Code to demonstrate the problem:
static void Main(string[] args)
{
var a = new A();
var b = new B();
Base<>[] all = new Base<>[] { a, b }; // doesn't work
}
class Base<T>
{
public string Caption { get { return typeof(T).ToString(); } }
}
class A : Base<A> { }
class B : Base<B> { }
Perhaps I went the wrong direction. Idea was to move Caption into base class (Base become generic). Non-generic version works without problems:
var all = new Base[] { a, b }; // no problems for as long as Base is not generic
There's no Type<?> in C# - you always have to specify a concrete generic type.
The only way around this is to make Base<T> inherit a non-generic base-class, or implement a non-generic interface. You could then use that as the type of the array.
EDIT:
In your case this is extremely simple, since the part of the interface you want doesn't include the generic type argument. So you can simply do either:
public abstract class Superbase
{
public abstract string Caption { get; }
}
public class Base<T>: Superbase
{
public override string Caption { get { return typeof(T).Name; } }
}
Or, using an interface:
public interface IBase
{
string Caption { get; }
}
public class Base<T>: IBase
{
public string Caption { get { return typeof(T).Name; } }
}
Your array would then be Superbase[] or IBase[], respectivelly. In both cases, you can see that I'm not actually providing an implementation - both the declarations are "abstract", in a sense.
In general, I'm trying to keep the non-generic stuff in a non-generic base class, rather than stuffing it in the derived generic classes. It just feels more clean :)
based on #Luaan ideea, here is an implementation:
class Program
{
static void Main(string[] args)
{
var a = new A();
var b = new B();
var arr = new Base[] { a, b};
foreach (var obj in arr)
Console.WriteLine(obj.Caption);
Console.ReadKey();
}
}
public class Base<T> : Base
{
public override string Caption
{
get { return typeof (T).ToString(); }
}
}
public class A : Base<A> { }
public class B : Base<B> { }
public abstract class Base
{
public abstract string Caption { get; }
}
Instead of trying to use inheritance (which will lead to more problems down the line), use an extension method instead:
public interface IClassAORClassB {}
class A : IClassAORClassB { }
class B : IClassAORClassB { }
public static class Captions
{
public static string Caption<T>(this T obj) where T : IClassAORClassB
{
return obj.GetType().ToString();
}
}
static void Main(string[] args)
{
var a = new A();
var b = new B();
var all = new IClassAORClassB[] { a, b }; // works just fine
Console.WriteLine(all[0].Caption()); // prints A
Console.WriteLine(all[1].Caption()); // prints B
}
I want to have a base class:
public class Base
{
public static T Instance
{
get
{
// do something to return new instance of inherit class from itself
}
}
}
Class1:
public class Class1 : Base
{
// method and properties here
public string Func1()
{
return 'class1';
}
}
Class2:
public class Class2 : Base
{
// method and properties here
public string Func1()
{
return 'class2';
}
}
I want it so that we can use Class1 or Class2 like this
public class Main
{
var a = Base<Class1>.Instance.Func1(); // return 'class1'
var b = Base<Class2>.Instance.Func1(); // return 'class2'
}
Please help me to do this.
This is not called dynamic but polymorphic. In this case achieved with generics. Your only remaining problem is calling the constructor, which becomes possible when you put a Type-constraint on <T>.
public class Base<T> where T : new()
{
public static T Instance
{
get
{
// do something to return new instance of inherit class from itself
return new T();
}
}
}
and then:
public class Class1 : Base<Class1> { ... }
public class Class2 : Base<Class2> { ... }
But note that a simpler solution could be achieved with virtual+override methods or with an interface.
Alternative suggestion with some tighter type constraints:
namespace My.Test
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine(Base<Class1>.Instance.Func1());
Console.WriteLine(Base<Class2>.Instance.Func1());
}
}
public abstract class Base
{
public abstract string Func1();
}
public sealed class Base<T> where T : Base, new()
{
public static T Instance
{
get { return new T(); }
}
}
public class Class1 : Base
{
public override string Func1() { return "class 1"; }
}
public class Class2 : Base
{
public override string Func1() { return "class 2"; }
}
}
I have the (pseudo) code:
public class GlobalClass
{
public GlobalClass()
{
var x = this.GetType().Name // Returns "Channels"
// WHAT TO DO HERE?
}
}
public class BaseClass
{
public string Title { get; set; }
}
And using this code:
public class Channels : GlobalClass
{
public Channels()
{
}
public class Channel : BaseClass
{
}
}
Where the comment is (// WHAT TO DO HERE?), I want to get the runtime type of BaseClass,
where in my sample code should return Channel.
I am open to different approaches, but only if it's accompanied with an explanation why I should change the code.
I think you need a generic class here, something like:
public class GlobalClass<T> where T : BaseClass
{
public GlobalClass()
{
var theType = typeof(T); //you got it
}
}
public class BaseClass
{
public string Title { get; set; }
}
public class Channel : BaseClass { }
public class Channels : GlobalClass<Channel> { }
You can use reflection like this:
using System.Reflection;
...
public class GlobalClass
{
public GlobalClass()
{
Type[] types = Assembly.GetExecutingAssembly ().GetTypes ();
foreach ( Type t in types )
{
if ( t.BaseType == typeof ( BaseClass ) )
{
Console.WriteLine ( "I found a class " + t.Name + " that subclass BaseClass" );
}
}
}
}
See also Stack Overflow question List of classes in an assembly.
is operator is just for that purpose.
getType() method with class Type can also be used.
class Example
{
static void ShowTypeInfo (object o)
{
Console.WriteLine ("type name = {0},
full type name = {1}", o.GetType(),
o.GetType().FullName );
}
public static void Main()
{
long longType = 99;
Example example= new Example();
ShowTypeInfo (example);
ShowTypeInfo (longType);
}
}
To get the runtime type of anything, you first need an object instance to get the type from. So with your given structure, that's not possible.
There are two possible approaches:
Add a BaseClass parameter to the constructor of your GlobalClass:
public class GlobalClass
{
public GlobalClass(BaseClass data)
{
var dataType = data == null ? null : data.GetType();
// do something with the type
}
}
public class Channels : GlobalClass
{
public Channels(Channel data) : base(data)
{
}
public class Channel : BaseClass
{
}
}
Pass the type to the constructor directly:
public class GlobalClass
{
public GlobalClass(Type actualType)
{
Debug.Assert(typeof(BaseClass).IsAssignableFrom(actualType));
}
}
public class Channels : GlobalClass
{
public Channels() : base(typeof(Channel))
{
}
public class Channel : BaseClass
{
}
}
If the structure for some reason doesn't allow generics here (as Danny Chen suggested), I'd personally prefer the second approach, since that doesn't need an actual instance.