How to Dynamic New Anonymous Class

How to dynamic new Anonymous Class?

Anonymous types are just regular types that are implicitly declared. They have little to do with dynamic.

Now, if you were to use an ExpandoObject and reference it through a dynamic variable, you could add or remove fields on the fly.

edit

Sure you can: just cast it to IDictionary<string, object>. Then you can use the indexer.

You use the same casting technique to iterate over the fields:

dynamic employee = new ExpandoObject();
employee.Name = "John Smith";
employee.Age = 33;

foreach (var property in (IDictionary<string, object>)employee)
{
Console.WriteLine(property.Key + ": " + property.Value);
}
// This code example produces the following output:
// Name: John Smith
// Age: 33

The above code and more can be found by clicking on that link.

Creating an anonymous type dynamically?

Only ExpandoObject can have dynamic properties.

Edit:
Here is an example of Expand Object usage (from its MSDN description):

dynamic sampleObject = new ExpandoObject();
sampleObject.TestProperty = "Dynamic Property"; // Setting dynamic property.
Console.WriteLine(sampleObject.TestProperty );
Console.WriteLine(sampleObject.TestProperty .GetType());
// This code example produces the following output:
// Dynamic Property
// System.String

dynamic test = new ExpandoObject();
((IDictionary<string, object>)test).Add("DynamicProperty", 5);
Console.WriteLine(test.DynamicProperty);

Dynamic construction of anonymous class confusion

This is a very simple question with a very complex answer. Please bear with me as I try to explain it.

Looking at the source code where the exception is raised in Class (I'm not sure why your stack trace doesn't give the line numbers in Class):

try
{
Class[] empty = {};
final Constructor<T> c = getConstructor0(empty, Member.DECLARED);
// removed some code that was not relevant
}
catch (NoSuchMethodException e)
{
throw new InstantiationException(getName());
}

you see that NoSuchMethodException is being rethrown as InstantiationException. This means there is not a no-arg constructor for the class type of object2.

First, what type is object2? With the code

System.out.println("object2 class: " + object2.getClass());

we see that

object2 class: class junk.NewMain$1

which is correct (I run sample code in package junk, class NewMain).

What then are the constructors of junk.NewMain$1?

Class obj2Class = object2.getClass();
try
{
Constructor[] ctors = obj2Class.getDeclaredConstructors();
for (Constructor cc : ctors)
{
System.out.println("my ctor is " + cc.toString());
}
}
catch (Exception ex)
{
ex.printStackTrace();
}

which gives us

my ctor is junk.NewMain$1(java.util.Calendar)

So your anonymous class is looking for a Calendar to be passed in. This will then work for you:

Object newObj = ctors[0].newInstance(Calendar.getInstance());

If you have something like this:

final String finalString = "I'm final :)";
final Integer finalInteger = new Integer(30);
final Calendar calendar = Calendar.getInstance();
Object object2 = new Object()
{
{
System.out.println("Instance initializing block");
System.out.println(finalString);
System.out.println("My integer is " + finalInteger);
System.out.println(calendar.getTime().toString());
}
private void hiddenMethod()
{
System.out.println("Use reflection to find me :)");
}
};

then my call to newInstance won't work because there are not enough arguments in the ctor, because now it wants:

my ctor is junk.NewMain$1(java.lang.Integer,java.util.Calendar)

If I then instantiate that with

Object newObj = ctors[0].newInstance(new Integer(25), Calendar.getInstance());

a peek inside using the debugger shows that finalInteger is 25 and not the final value 30.

Things are slightly complicated because you're doing all of the above in a static context. If you take all your code above and move it into a non-static method like so (remember, my class is junk.NewMain):

public static void main(String[] args)
{
NewMain nm = new NewMain();
nm.doIt();
}

public void doIt()
{
final String finalString = "I'm final :)";
// etc etc
}

you'll find the ctor for your inner class is now (removing my added Integer reference):

my ctor is junk.NewMain$1(junk.NewMain, java.util.Calendar)

The Java Language Specification, section 15.9.3 explains it this way:

If C is an anonymous class, and the direct superclass of C, S, is an
inner class, then:

  • If the S is a local class and S occurs in a static context, then
    the arguments in the argument list, if any, are the arguments to
    the constructor, in the order they appear in the expression.
  • Otherwise, the immediately enclosing instance of i with respect to
    S is the first argument to the constructor, followed by the
    arguments in the argument list of the class instance creation
    expression, if any, in the order they appear in the expression.

Why does the anonymous constructor take arguments at all?

Since you can't create a constructor for an anonymous inner class, the instance initializer block serves that purpose (remember, you only have one instance of that anonymous inner class). The VM has no knowledge of the inner class as the compiler separates everything out as individual classes (e.g. junk.NewMain$1). The ctor for that class contains the contents of the instance initializer.

This is explayed by JLS 15.9.5.1 Anonymous Constructors:

...the anonymous constructor has one formal parameter for each actual
argument to the class instance creation expression in which C is
declared.

Your instance initializer has a reference to a Calendar object. How else is the compiler going to get that runtime value into your inner class (which is created as just a class for the VM) except through the constructor?

Finally (yay), the answer to the last burning question. Why doesn't the constructor require a String? The last bit of JLS 3.10.5 explains that:

Strings computed by constant expressions are computed at compile time
and then treated as if they were literals.

In other words, your String value is known at compile time because it's a literal so it does not need to be part of the anonymous constructor. To prove this is the case we'll test the next statement in JLS 3.10.5:

Strings computed by concatenation at run time are newly created and
therefore distinct.

Change your code thusly:

String str1 = "I'm";
String str2 = " final!";
final String finalString = str1 + str2

and you'll find your ctor is now (in the non-static context):

my ctor is junk.NewMain$1(junk.NewMain,java.lang.String,java.util.Calendar)

Phew. I hope this makes sense and was helpful. I learned a lot, that's for sure!

How to define anonymous object property dynamically?

Anonymous types are immutable, you can only create and set properties when creating the instance. This means you need to create the exact object you need. So you could do something like this instead:

if (fields.Contains("picture_url"))
{
return Ok(new
{
id = userProfile.UserId,
email = userProfile.Email,
name = userProfile.Name,
picture_url = "path"
});
}

return Ok(new
{
id = userProfile.UserId,
email = userProfile.Email,
name = userProfile.Name
});

Another option is to used a Dictionary<string, object>. For example:

var userInfo = new Dictionary<string, object>
{
{"id", userProfile.UserId},
{"email", userProfile.Email},
{"name", userProfile.Name}
};

if (fields.Contains("picture_url"))
{
// error in this line
userInfo.Add("picture_url", "path");
}

return Ok(userInfo);

This object will serialise to the same JSON structure:

{"id":1,"email":"email@somewhere.com","name":"Bob","picture_url":"path"}

Casting anonymous type to dynamic

Anonymous objects are internal, which means their members are very restricted outside of the assembly that declares them. dynamic respects accessibility, so pretends not to be able to see those members. If the call-site was in the same assembly, I expect it would work.

Your reflection code respects the member accessibility, but bypasses the type's accessibility - hence it works.

In short: no.

Adding extra properties to an anonymous class

Thanks to a comment from Patryk I gave it a go using an ExpandoObject and got it working like this:

dynamic viewData = new ExpandoObject();
viewData.@class = cssClasses;
if (controlId != null)
viewData.id = controlId;
if (title != null)
viewData.title = title;
// put the result into a route value dictionary so that MVC's EditorFor (etc) can interpret it
var additionalViewData = new RouteValueDictionary(viewData);

That last line was the key to getting it working in MVC so that it could be passed as the additionalViewData parameter in EditorFor etc.

In the situations where I'm being passed an anonymous class and need to add to it myself, I'm using reflection (and taking advantage of ExpandoObject implementing IDictionary). Here's the unit test I wrote to check that it works:

[TestMethod]
public void ShouldBeAbleToConvertAnAnonymousObjectToAnExpandoObject()
{
var additionalViewData = new {id = "myControlId", css = "hide well"};
dynamic result = new ExpandoObject();
var dict = (IDictionary<string, object>)result;
foreach (PropertyInfo propertyInfo in additionalViewData.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public))
{
dict[propertyInfo.Name] = propertyInfo.GetValue(additionalViewData, null);
}
Assert.AreEqual(result.id, "myControlId");
Assert.AreEqual(result.css, "hide well");
}


Related Topics



Leave a reply



Submit