I ran into a problem today when trying to set a field using FieldInfo.SetValue() passing a DynamicObject as the second argument. In my case, the field is a Guid and the DynamicObject should be able to convert itself to a one (using TryConvert) but it fails with an ArgumentException.
Some code that shows the problem:
// Simple impl of a DynamicObject to prove point
public class MyDynamicObj : DynamicObject
{
public override bool TryConvert(ConvertBinder binder, out object result)
{
result = null;
// Support converting this to a Guid
if (binder.Type == typeof(Guid))
{
result = Guid.NewGuid();
return true;
}
return false;
}
}
public class Test
{
public Guid MyField;
}
class Program
{
static void Main(string[] args)
{
dynamic myObj = new MyDynamicObj();
// This conversion works just fine
Guid guid = myObj;
var test = new Test();
var testField = typeof(Test).GetField("MyField");
// This, however, fails with:
// System.ArgumentException
// Object of type 'ConsoleApplication1.MyDynamicObj' cannot be converted to type 'System.Guid'.
testField.SetValue(test, myObj);
}
}
I'm not very familiar with the whole dynamicness of C# 4, but this felt to me like something that should work.. What am I doing wrong? Is there another way of doing this?
No, this shouldn't work - because the dynamic portion ends where your code ends. The compiler is calling a method with a signature of
void SetValue(Object obj, Object value)
That method call is dynamic, but it's just going to end up passing in a reference to the instance of MyDynamicObj. The call is resolved at execution time, but nothing in SetValue knows anything about the dynamic nature of the object whose reference you're passing in.
Basically you need to perform the dynamic part (the conversion in this case) in your code - the bit that involves the C# 4 compiler doing all its tricks. You've got to perform that conversion, and then you can call SetField.
To put it another way - it's a bit like calling SetField with a field of type XName, but passing in a string. Yes, there's a conversion from string to XName, but it's not SetField's job to work that out. That's the compiler's job.
Now, you can get this to work by making the compiler do some of the work, but you still need to do some with reflection:
static void Main(string[] args)
{
dynamic myObj = new MyDynamicObj();
var test = new Test();
var testField = typeof(Test).GetField("MyField");
var method = typeof(Program)
.GetMethod("Convert", BindingFlags.Static | BindingFlags.NonPublic);
method = method.MakeGenericMethod(testField.FieldType);
object converted = method.Invoke(null, new object[] {myObj});
testField.SetValue(test, converted);
}
static T Convert<T>(dynamic input)
{
return input;
}
You need an explicit cast to invoke the TryConvert:
testField.SetValue(test, (Guid)myObj);
Not sure if this is what you need though. Maybe there's some way to reflectively say ((DynamicObject)myObj).TryConvert(/*reflected destination type here*/, result)
Other attempts that failed, some of them require things like a certain interface be implemented, so they basically don't make use of TryConvert but maybe an alternative way to accomplish what you want:
Type secondType = testField.FieldType;
TypeConverter tc = TypeDescriptor.GetConverter(typeof(MyDynamicObj));
object secondObject = tc.ConvertTo(myObj,typeof( Guid));
//var secondObject = Convert.ChangeType(myObj, secondType);//Activator.CreateInstance(secondType);
//secondObject = myObj;
testField.SetValue(test, secondObject);
Related
I'm trying to wrap my head around abstract by implementing a Set data-type, like so:
abstract Set<T>(Map<T, Bool>) {
public inline function new() {
this = new Map<T, Bool>();
}
public inline function has(item:T):Bool {
return this.exists(item);
}
public inline function add(item:T):Set<T> {
this.set(item, true);
return null;
}
public inline function remove(item:T):Set<T> {
this.remove(item);
return null;
}
public inline function iterator():Iterator<T> {
return this.keys();
}
}
The compiler doesn't like this, though. It tells me Set.hx:8: characters 11-29 : Abstract Map has no #:to function that accepts IMap<util.Set.T, Bool>
I don't really understand this at all, since if I change the constructor to
public inline function new(val:Map<T, Bool>) {
this = val;
}
and then instantiate with var set = new Set(new Map());, it works.
That's pretty gross, though. I'd like the ability to instantiate Sets without exposing the underlying implementation. Ultimately, I'd prefer a constructor with the signature new(?initial:Iterable<T>). Is this possible? Am I misunderstanding something?
The problem is that currently it's impossible to instantiate Map without they key type being known (and since Set.T is a free type parameter, this doesn't work). However since the constructor is inline, T may well be known at the call site. The problem is that the compiler still tries to generate Set.new. You can avoid this by prefixing it with #:extern. Working example: https://try.haxe.org/#1D06C
I need help to use the below piece of code to call Method1. There are no compilation issues but while running the code Method1 is not invoked. I searched the net a lot but didn't find a solution.
public static class Test12
{
public static Test12.ByteDelegate PropertyValue { get; set; }
public delegate byte[] ByteDelegate(byte p1, byte[] p2);
}
The above class has to be used like this:
class Abc
{
internal void Stat()
{
Test12.Propertyvalue = Method1;
}
private byte[] Method1(byte p1, byte[] p2)
{
byte[] abc = ...;
return abc;
}
}
If I am creating an instance of the delegate in stat method and using it like:
Method1(param1,param2);
Then the Method1 in invoked, but if I use it like this:
Test12.PropertyValue = Method1(param1,param2);
compiler is throwing an error saying missing typecast. Can anybody please tell me how to invoke Method1 using Test12.PropertyValue = Method1;?
You receive an error message because when you try to assign the method to the delegate you are using the following line of code:
test12.propertyvalue = method1(param1, param2);
Which firstly calls the method1 and then what it returns it's trying to assign to the delegate, and the error message occurs saying that you can't assign a byte array to a delegate, as #Jon said in the comment. They are different types so you can't assign one to another, at least not without a cast or a conversion method.
In order to invoke the method1 from the delegate, after you do the assignation like this:
test12.propertyvalue = method1;
you can simply call the delegate, as it would be your method:
test12.propertyvalue(param1 , param2);
If one pass a method as a funarg, how one can tell if passed function is a method, and get `this' object of a method is?
class A {
public function f():Void{
trace("f");
}
}
class B {
static function withFunarg(f:Void->Void):Void{
//HERE
}
public static function main(){
var a = new A();
withFunarg(a.f);
}
}
You cannot and there is no way to retrieve this. But it seems to me like an anti-pattern trying to do that. If you want the method and the container you can define a typedef:
typedef F = {
f : Void -> Void
}
Now you have the method and the container.
Haxe doesn't offer a cross-platform way to do that and it is generally not recomended.
But if you ultimately need this feature, you can use some platform-specific ways.
For example on js the following will work(at least on current haxe dev version):
static function getThis(f:Dynamic):Dynamic{
return (f.scope && f.method) ? f.scope : null;
}
It will return the object if the function is a method and a null otherwise. Result on calling on non-function is unspecified.
If you want to get the implicit `this' argument of a method, you have to make it explicit, like this
static function withMethodFunarg(o:{}, f:{}->Void):Void{
//HERE you have both object and function on this object
trace(o);
f(o);
}
public static function main(){
var a = new A();
withMethodFunarg(a,function(a){a.f()});
}
Which is, actually, pretty straight-forward: function is a function, no implicits, method caller is a method caller.
I need to be able to call a method and pass in an object of an unknown type
but then have the correct overload called. I also need a default implementation that accepts
object as its parameter type. What I'm seeing is that the default overload is the only one that ever gets used.
Here's the gist of what I'm trying to do:
class Formatter
{
private object Value;
public Formatter(object val){
Value = val;
}
public override string ToString()
{
return Format(Value);
}
private string Format(object value)
{
return value.ToString();
}
private string Format(DateTime value)
{
return value.ToString("yyyyMMdd");
}
}
Ok, so far so good. Now I want to be able to do this:
public static class FancyStringBuilder()
{
public static string BuildTheString()
{
var stringFormatter = new Formatter("hello world");
var dateFormatter = new Formatter(DateTime.Now);
return String.Format("{0} {1}", stringFormatter, dateFormatter);
}
}
The result of FancyStringBuilder.BuildTheString() is "hello world 2012-12-21 00:00:00.000", when I expected "hello world 20121221"
The problem is that the overload that accepts a DateTime is not being called, instead defaulting to the overload which accepts an object. How can I call the proper method without resorting to a messy switch statement?
In Formatter.ToString(), the override Formatter.Format(object) is always called. This is because the overload resolution happens at compile-time, not run-time. At compile-time, the only thing known about Value is that it's an object.
If you really want to distinguish incoming types, you'll need to do so in Formatter's constructor. In this case, rather than hanging on to the object, you could just call ToString() immediately and only store the formatted result:
class Formatter
{
string formattedValue;
public Formatter(object value)
{
formattedValue = value.ToString();
}
public Formatter(DateTime value)
{
formattedValue = value.ToString("yyyyMMdd");
}
public string ToString()
{
return formattedValue;
}
}
Note that this does assume that your object isn't changing between the time you create the Formatter object and the time Formatter.ToString() is called, or at the very least that it's okay to take a snapshot of the string representation at the time the Formatter is created.
This also assumes that you know the incoming types at compile-time. If you want a truly run-time-only solution, you'll have to use the "is" operator or a typeof() comparison.
If your goal is just to provide custom ToString() formatting based on the incoming type, I'd probably do it using a list that maps from types to format strings:
static class Formatter
{
private static List<Tuple<Type, string>> Formats;
static Formatter()
{
Formats = new List<Tuple<Type, string>>();
// Add formats from most-specific to least-specific type.
// The format string from the first type found that matches
// the incoming object (see Format()) will be used.
AddMapping(typeof(DateTime), "yyyyMMdd");
// AddMapping(typeof(...), "...");
}
private static void AddMapping(Type type, string format)
{
Formats.Add(new Tuple<Type, string>(type, format));
}
public static string Format(object value)
{
foreach (var t in Formats)
{
// If we find a type that 'value' can be assigned to
// (either the same type, a base type, or an interface),
// consider it a match, and use the format string.
if (t.Item1.IsAssignableFrom(value.GetType()))
{
return string.Format(t.Item2, value);
}
}
// If we didn't find anything, use the default ToString()...
return value.ToString();
}
}
With that, calling code then looks like:
Console.WriteLine(
"{0} {1}",
Formatter.Format(DateTime.Now),
Formatter.Format("banana"));
I think this is because the class constructor takes an object as parameter, and then assign that object to variable Value which is also an object. There for calling Format(object) since Value is of type object
Try this
public override string ToString()
{
if(Value is DateTime)
return Format(Convert.ToDateTime(Value)); //this should call the right method
return Format(Value); //works for other non-custom-format types e.g. String
}
I would like to dynamically invoke a Class's Property via a String. In the following code, I can dynamically invoke a Class's Function via a String.
var myClass:Class = getDefinitionByName("myPackage.MyClass") as Class;
myClass["myStaticMethod"]();
where MyClass is defined as:
package myPackage {
public class MyClass {
public function MyClass() {}
public function myMethod():void {};
public static function myStaticMethod():void {};
public static function get myProperty():Object { return null; }
}
}
However, a Property, such as MyClass.myProperty is not a Function. So,
var myClass:Class = getDefinitionByName("myPackage.MyClass") as Class;
myClass["myProperty"]();
throws an error: TypeError: Error #1006: value is not a function because myProperty is not a Function.
Is there any way to do this dynamically via Strings?
Thanks for the help.
To solve this issue, I simply needed to remove the () from the code. That is, the new code looks like:
var myClass:Class = getDefinitionByName("myPackage.MyClass") as Class;
myClass["myProperty"]; // This works.
The Answer of Alex will indeed works properly, but only if you have the String written properly. Else you get this error thrown at you: TypeError: Error #1006: value is not a function. To avoid this you could try test if the property or method is defined before using it. Like so:
if(myClass["myProperty"] != undefined)
{
...
}
Anyhow, in your specific example you are requesting a getter, and that's why you had to remove the () from your source. If you would be needing a method, I would also recommend you to save the method as a function:
var myFunction: Function = myClass["theFunction"];
And then to use either the call or the apply methods.
myFunction.call(null, myParam);
IF you are interested in studying all the methods that an Object has and comparing them to a String. Consider also:
var child:Sprite = new Sprite();
var description:XML = describeType(child);
var methodList: XMLList = description.descendants('method');
The attributes of a <method/> node are:
name: The name of the method.
declaredBy: The class that contains the method definition.
returnType: The data type of the method's return value.
I hope this helps out, let me know if you found it useful.