|
I was writing some code where I needed to create an instance of an object and then set some values for properties on that object. Seems like a pretty easy task, if you know the class type (and have a reference to it) at compile-time, but what do you do if all you have is a string representing the name of the class, and strings representing the names of various properties?
Of course, as a .NET programmer, I know that I can use Reflection. I have to ensure that I have a reference to the class that I intend to instantiate, but I can instantiate the class by name (provided I know the namespace as well) and I can manipulate properties by name as well. Take a look at the following code:
object o = Assembly.GetExecutingAssembly().CreateInstance("DynamicInstantation.Customer");
Type t = o.GetType();
PropertyInfo pi = t.GetProperty("FirstName");
pi.SetValue(o, "Kevin", null);Console.WriteLine(string.Format(
"New Property value is {0}", pi.GetValue(o, null)));
Yes, I am aware that I typo'd the namespace. So sue me. This works as intended and the output from running the program does indeed indicate that the FirstName property of my instance of the Customer class has been set to "Kevin". That's great, but what you might not have noticed here is that I have foreknowledge of the data type of the destination property. What the hell does that mean? It means that this works because I knew at compile-time that FirstName is a string. What happens if I get a string from my data source and try and set the Age (int) property to the string "42"? This is what happens:
Object of type 'System.String' cannot be converted to type 'System.Int32'
Pretty, isn't it? Of course we know that "42" can be Int32.Parse'd into an int..but, in order to know that we'd have to do runtime interrogation of the data types of either the destination or the source in order to use the Convert class to do on-the-fly data type conversion. In short, I'm opening a can of worms. LINQ might be able to help simplify things here by allowing me to write some type conversion language extensions to make the experience simpler, but it won't change the underlying nature of the beast.
So what does this look like in Objective-C? It might be a bit of an unfair comparison because Objective-C as a language is more dynamic than C#. However, I still think it's worth looking at given how C# and Objective-C are both peers in the sense that Objective-C is the driving language for Cocoa and C# is one of the driving languages for the .NET Framework. Here is some Objective-C code that creates an instance of a class based on it's string name at runtime and then sets the value of a property at runtime:
id theObject = [[NSClassFromString("Customer") alloc] init];
[theObject setValue:@"Kevin" forKey:@"FirstName"];
NSLog(@"The new property value is %@", [theObject valueForKey:@"Customer"]);The first time I compiled this and it ran, I was shocked. I kept thinking, "Holy crap there's no way I should be able to do this... it's too...simple...". But it worked. Not only did it work, but if you are familiar with Key-Value-Coding, then you know that calling setValue:forKey: on a class actually invokes all notification triggers..meaning if you change values this way, bound GUI will be automatically notified, and this integrates well with the undo manager system. How cool is that?? To get that in C# we'd have to implement INotifyPropertyChanged on each model object and manually fire the changed event when the property changes. If you saw my WWDC presentation, then you'll know that one complain I have about "undo" support in C#/WPF is that you are not notified before a value changes, you are only notified after it changes. Which is utter crap, if you ask me.
So, given the code above...what happens if you try and send the value "42" to an integer-type column? It works. KVC is going to automatically attempt to coerce the source value into the type of the destination property. This means that you don't need to know at compile-time the data types of the properties, nor do you need to write code to interrogate the values. Obviously this has limitations, but for basic scalar primitives like floats and doubles and ints and strings, it seems to work like a charm.
Also keep in mind that while there's only a couple of lines difference in the code now, think of what it would look like if you had to deal with a lot of different properties. I can see the C# code getting ugly very fast without writing some helper code to keep things tidy.
The more I use Objective-C the more I become aware of it's elegance and power. I just really, really like that language.
Automatic type conversion is a great feature from other brilliant languages
like Perl and VB
/sarcasm
Don't confuse this with lack of strong typing. Objective-C is real C, with
strong types. The "automatic" nature of this just means that under the hood
Cocoa is doing the interrogation of the destination type and coercing the
source value to that type for me.
Hi. I am assuming that your objc class "kevin" has an accessor method that
deals with the variable "FirstName" (to support the KVC). What would
happen if there was an ivar called "FirstName", but no accessors? Would
this put your C# comparison on a more even footing?
cal: All you need in that case is to hook up the class method +
(BOOL)accessInstanceVariablesDirectly to return YES, and it continues to
work. However, KVC is somewhat nicer than that, because if you have a
-(void)setFirstName:(NSString *)name, you don't need an instance variable
that corresponds with it (useful if you have values which are merely
derived from other ivars, but don't need one themselves. As an example,
you could have a "date" value & ivar, and a timeSinceDate value - you can
still use setTimeSinceDate: without a reserved ivar for it! This example
is somewhat contrived in its simplicity, but I believe it illustrates the
point nicely)
phil: that's pretty cool. still trying to get my head around the whole kvc
thing ... but as kevin states in the article - cocoa is full of pleasant
surprises :)
Kevin, chide not Asd for it's obvious he knows not what he says. All of
those years coding in ASP.net has no doubt addled his brain. ;)
As Phil mentions... if you don't have an ivar defined, then let's say you
try and set the value for key "FirstName", (which is actually cased
wrong..it should be firstName), then if you have an accessor called
setfirstName:(NSString *)theName then ObjC is going to call that instead of
setting the accessor, which gives you all kinds of extremely powerful
possibilities for "virtual" properties, two-way calculated fields, etc.
Also keep in mind that any key that you have defined can be _bound to your
GUI_. Extremely powerful stuff.
Hello Kevin, you may be aware of this already, but in .NET, you can easily
do this with System.ComponentModel.TypeDescriptor.GetConverter(targetType).
ConvertFromString(text). I have used this a lot, and it has worked well for
my scenarios. Another programer and I wrote an extension for MbUnit se we
could completely specify unit tests in a data driven way. We used this for
passing parameters to class constructors, properties, fields, method
parameters and then to coerce expected values to the right type for
asserts. It was all specified in strings and then everything was converted
to the aproppriate types in runtime. I can say we even saw surprisingly
good performance.
Yes Obj-C comes out on top again, and I'm noticing a pattern with your
posts. The features of Cocoa that bring out the expression of wonder in you
are all possible because Obj-C is dynamic. These things are impossible to
do simply and elegantly without that flexibility. The best thing is that
Obj-C supports static typing too, choose to use compile time type checking
to improve your code quality but don't be shackled by it.