How To Reference A Property By Name Without Using String Constants

by Jon Davis 13. June 2010 02:39

I’m working on some code here where I’m loading an entity using a dictionary. I’m not using .NET 4.0 so I’m not going to get to use the dynamic keyword and its IDictionary implementation of properties reflection.

My original code looked something sort of like this:

public void Populate(IDictionary<string, object> values) 
{
	foreach (var kvp in values) {
		var key = kvp.Key;
		var value = kvp.Value;
		switch (key) 
		{
			case "FirstName":
				this.FirstName = value;
				break;
			case "LastName":
				this.LastName = value;
				break;
			// and so on and so forth
		}
	}
}

This looks sloppy. First of all, I don’t want to have to declare each and every property again for its setter just to support IDictionary. Secondly, what if I rename my properties? Then I’d have to go back and update this switch…case statement again. I want to use a strongly-typed property reference, so that if I rename the property I can use the IDE’s built-in refactoring functionality with which all uses of that property are automatically updated. I don’t get that benefit if I’m using string constants.

I’d come across some interesting tutorials in the past that addressed this problem, and I thought I’d give it my own go based on their insight. My revised solution is not by any means original. (Nothing I ever do is, I suppose.)

To make a long story short, I flexed some recently learned LINQ expression muscle on a member evaluator expression to obtain the strongly typed property’s name. In my implementation (not entirely shown here) I actually do need to verify the property by name because I have a bunch of setter logic that I don’t expose here, such as replacing the value being an IEnumerable<string> with logic that clears an ObservableCollection<string> and populates it with the value. Anyway, here’s the basic flow:

public override void Populate(IDictionary<string, object> values)
{
	foreach (var kvp in values) {
		if (key == base.ResolvePropertyName(() => this.SpecialProperty))
		{
			// this.SpecialProperty = value;
            // do something special with setter here
		}
		else if (key == base.ResolvePropertyName(() => this.AnotherSpecialProperty))
		{
			// this.AnotherSpecialProperty = value;
            // do something special with setter here
		}
		else 
		{
			base.Populate(key, value);
		}
	}
}

/// and in the base class ....

/// <summary>
/// Returns the name of the property. Syntax:
/// <code>
/// var propertyName = ResolvePropertyName(() =&gt; this.MyProperty)
/// </code>
/// </summary>
/// <param name="e"></param>
/// <returns></returns>
protected string ResolvePropertyName(Expression<Func<T>> e)
{
    if (e.NodeType == ExpressionType.Lambda)
    {
        return ResolvePropertyName(((LambdaExpression)e).Body);
    }
    if (e.NodeType == ExpressionType.MemberAccess)
    {
        return ((MemberExpression)e).Member.Name;
    }
    throw new InvalidOperationException("Given expression is not type MemberAccess.");
}

private static Dictionary<Type, List<PropertyInfo>> TypeProperties
    = new Dictionary<Type, List<PropertyInfo>>();
/// <summary>
/// Populates a property of the current entity with the the given key/value pair.
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
public void Populate(string property, object value)
{
    var t = this.GetType();
    List<PropertyInfo> pis;
    if (!TypeProperties.ContainsKey(t))
    {
        TypeProperties[t] = t.GetProperties().ToList();
    }
    pis = TypeProperties[t];
    var pi = pis.Find(pif => pif.Name == property);
    if (pi == null) throw new KeyNotFoundException("Property does not exist: \"" + property + "\"");
    pi.SetValue(this, value, null);
}

The Populate(key,value) method is basic .NET 2.0 reflection with some sorta-kinda basic caching.  The method above it is using LINQ expressions to extract the member access invocation as expressed in the LINQ statement.

I wrote a unit test to invoke this and stepped through it with the debugger. Seems to work fine.

After further tweaking, I migrated the base class functions to a utility class so that these functions can be used with anything, not just with objects that inherit a common base class. I also improved the reflection caching of Populate() so that the properties are properly hashed on their property names.

public static class ObjectUtility
{

    /// <summary>
    /// Returns the name of the property. Syntax:
    /// <code>
    /// var propertyName = obj.ResolvePropertyName(() =&gt; obj.MyProperty)
    /// </code>
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="obj"></param>
    /// <param name="expr"></param>
    /// <returns></returns>
    public static string ResolvePropertyName<T>(this object obj, Expression<Func<T>> expr)
    {
        return ResolvePropertyName(expr);
    }

    /// <summary>
    /// Returns the name of the property. Syntax:
    /// <code>
    /// var propertyName = ObjectUtility.ResolvePropertyName(() =&gt; obj.MyProperty)
    /// </code>
    /// </summary>
    /// <param name="e"></param>
    /// <returns></returns>
    public static string ResolvePropertyName(Expression e)
    {
        if (e.NodeType == ExpressionType.Lambda)
        {
            return ResolvePropertyName(((LambdaExpression)e).Body);
        }
        if (e.NodeType == ExpressionType.MemberAccess)
        {
            return ((MemberExpression)e).Member.Name;
        }
        throw new InvalidOperationException("Given expression is not type MemberAccess.");
    }

    private static Dictionary<Type, Dictionary<string, PropertyInfo>> TypeProperties
        = new Dictionary<Type, Dictionary<string, PropertyInfo>>();
    /// <summary>
    /// Populates a property of an object with the the given key/value pair.
    /// </summary>
    /// <param name="obj"></param>
    /// <param name="property"></param>
    /// <param name="value"></param>
    public static void Populate(this object obj, string property, object value)
    {
        var t = obj.GetType();
        if (!TypeProperties.ContainsKey(t))
        {
            TypeProperties[t] = new Dictionary<string, PropertyInfo>();
            var lst = t.GetProperties().ToList();
            foreach (var item in lst)
            {
                if (!TypeProperties[t].ContainsKey(item.Name))
                {
                    TypeProperties[t][item.Name] = item;
                }
            }
        }
        var pisdic = TypeProperties[t];
        var pi = pisdic.ContainsKey(property) ? pisdic[property] : null;
        if (pi == null) throw new KeyNotFoundException("Property does not exist: \"" + property + "\"");
        pi.SetValue(obj, value, null);
    }
}

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: ,

C#

Gemli.Data: Basic LINQ Support

by Jon Davis 6. June 2010 04:13

In the Development branch of Gemli.Data, I finally got around to adding some very, very basic LINQ support. The following test scenarios currently seem to function correctly:

var myCustomEntityQuery = new DataModelQuery<DataModel<MockObject>>();

// Scenarios 1-3: Where() lambda, boolean operator, method exec
var linqq = myCustomEntityQuery.Where(mo => mo.Entity.MockStringValue == "dah");
    linqq = myCustomEntityQuery.Where(mo => mo.Entity.MockStringValue != "dah");
    // In the following scenario, GetPropertyValueByColumnName() is an explicitly supported method
    linqq = myCustomEntityQuery.Where(mo=>((int)mo.GetPropertyValueByColumnName("customentity_id")) > -1);

// Scenario 4: LINQ formatted query
var q = (from mo in myCustomEntityQuery
         where mo.Entity.MockStringValue != "st00pid"
         select mo) as DataModelQuery<DataModel<MockObject>>;

// Scenario 5: LINQ formatted query execution with sorted ordering
var orderedlist = (from mo in myCustomEntityQuery
                   where mo.Entity.MockStringValue != "def"
                   orderby mo.Entity.MockStringValue
                   select mo).ToList();

// Scenario 6: LINQ formatted query with multiple conditions and multiple sort members
var orderedlist = (from mo in myCustomEntityQuery
                    where mo.Entity.MockStringValue != "def" && mo.Entity.ID < 3
                    orderby mo.Entity.ID, mo.Entity.MockStringValue
                    select mo).ToList();

This is a great milestone, one I’m very pleased with myself for finally accomplishing. There’s still a ton more to do but these were the top 50% or so of LINQ support scenarios needed in Gemli.Data.

Unfortunately, adding LINQ support brought about a rather painful rediscovery of critical missing functionality: the absence of support for OR (||) and condition groups in Gemli.Data queries. *facepalm*  I left it out earlier as a to-do item but completely forgot to come back to it. That’s next on my plate. *burp*

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags:

C# | Open Source | Pet Projects | Software Development | LINQ

HTC EVO 4G Is The Shiznat

by Jon Davis 5. June 2010 23:49

Friday I got my HTC EVO 4G, my first Android phone and my replacement for my first-generation iPhone 2G. I had considered the iPhone 3Gs and also bought the latest generation of the iPod Touch to see if I could get away with just VOIP (bad idea, quickly resold it on eBay), and I’ve seen what’s coming from iPhone OS 4.0. Frankly, I don’t care about the iPhone anymore, not after Steve Jobs urinated on us cross-platform devs with his laughably rude EULA provisions (“only code that was originally written in Obj-C/C/C++ using the SDK-provided Xcode IDE, no cross-compilers and no third party libraries”).

Fortunately, this didn’t have to be a boycott. I know that Apple is about to announce the next-gen iPhone, but I honestly believe that the next iPhone will be only on par with, and not superior to, the HTC EVO 4G.

I was quite pleasantly surprised by how feature-packed, how beautiful, and how responsive and usable the HTC EVO 4G is. After a day or two of getting used to it, I find the well-powered Android phone to be much better than I anticipated, even far better than the iPhone as the iPhone stands today. My biggest fears about Android was that with it being VM-based it would be extremely slow, since every Android I’ve seen is slow, even on the home screen as users slide the application list up and down. But I figured the 1GHz processor on the HTC EVO 4G might make up for it enough to be able to perform at least on par with the iPhone 3G, though perhaps not on par with the iPhone 3Gs. It turned out I was right. The monster device strongly outperforms my now-obsoleted iPhone 2G and doesn’t seem to flinch like all the other Android devices I’ve seen. And when the new Android v2.2 is installed on this thing, it’ll be at least twice as fast (reports say even faster, up to 5x).

Big plus’s:

  • The display size and resolution on the HTC EVO 4G is huge. No, it’s monstrous. And the display quite brilliant if you ask me, maybe not quite as true-color as the iPhone but certainly in its ball park.
  • The various connectivity features on the HTC EVO 4G are ridiculously numerous: 3G (turns out to be pretty fast in Scottsdale), 4G (where available, not here), Wi-Fi, Bluetooth, USB, and GPS. You can toggle each one of these on or off. The USB option will let you mount its installed Mini-SD card as a hard drive on the PC, or fire up HTC Sync to synchronize your Outlook or Windows contacts.
  • Yay, I finally have a clock on my smartphone that I can set to “just clock” mode for sleeping or for my desk, it’s a single app that manages the alarm clocks as well, and I didn’t need to download a marketplace app for any of that. Woohoo!
  • 8-megapixel camera. Just 8? Pfft ..  (My Nikon D50 DSLR camera only has 6 megapixels.)
  • An extra front-facing camera! I don’t see myself using it today, but I might next year! Maybe I can get my parents to use their webcam with software that’ll work with my Android, too.
  • Social app integration extremities abound. I think at least five of the apps I installed have Twitter integration support, three of the apps have Facebook integration support, and the OS itself (perhaps by way of HTC’s shell extensions?) is among them for Facebook and Twitter.
  • The device fully, 100% supports Google Goggles and Layar. “zomg” and stuff! This is like it’s straight out of the sci-fi movies.
  • Supports Internet tethering and being a WiFi hotspot. W00t!!
  • I love the vibrator feedback as you tap on things. Tapping keys, for example, makes the whole unit shake a little bit for a tiny fraction of a second, as if to suggest a gentle nudge. Really makes it feel like it’s alive and interacting with you.

My opinion-neutral observations:

  • The ringtones are many, with lots of curious noises, but most of them are synthesized music tidbits rather than noisy sound effects like car horns. The “traditional” ringtones like those of the old-fashioned telephone are rather crappy in quality, with abrupt cut-offs, etc. But overall I think I prefer the Android ringtones over the iPhone ringtones.
  • I desperately need a dock to keep it charged while keeping it upright in portrait position. It has a “kickstand” (which is cool) but you can’t use it in portrait position while charging it.
  • People aren’t kidding when they say that the battery life on this thing is pretty limited. I feel like I spend more time charging it than I do emptying it (though this isn’t actually true, and it will stay charged for some time if you’re not constantly downloading stuff or constantly poking at it). However, I’m not too upset about this; I am rarely away from  a power source (such as a PC I can connect it to via USB), and I plan on getting two docks, one for home and one for work. I am also hoping that a better battery will come out for it in a year or two.
  • 4G isn’t available in the Phoenix metro. My boss says it’ll be here in January of next year. I’m skeptical; we’ll see.

My complaints (so far):

  • The keyboard and textbox inputs are actually kind of nice, but often annoying. During setup, for example, while entering my mailing address I needed to add a line break, but the textbox was one-line, so it still allowed for a line break but its visibility was cropped off. The auto-suggestion functionality takes a lot of getting used to as it seems to be a lot more intrusive than on the iPhone, swapping my text out even when I just hit the Shift key rather than waiting for me to hit the spacebar or period or other symbol.
  • HTC Sync truly stinks. They don’t have 64-bit support (I use 64-bit Office 2010 everywhere) and there’s no synchronization of, say, shared documents, photos, or music. Just contacts. Bleh. Fortunately, one can use SyncToy easily with the mount-as-hard-drive USB support.
  • The Android marketplace is horrifically disorganized. The categories are way too sparse. And there’s no sort ordering with highest rated apps at top. It’s just a mess.
  • Games and other multimedia apps from the marketplace are natively low-resolution on this thing, and look blurry when stretched to the display resolution of the huge EVO 4G. Even the rich 3D games I tried look blurry.

But overall, I’m very happy. Neener.

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: , , ,

General Technology | Android | Mobile Devices


 

Powered by BlogEngine.NET 1.4.5.0
Theme by Mads Kristensen

About the author

Jon Davis Jon Davis (aka "stimpy77") has been a programmer, developer, and consultant for web and Windows software solutions professionally since 1997, with experience ranging from OS and hardware support to DHTML programming to IIS/ASP web apps to Java network programming to Visual Basic applications to C# desktop apps.
 
Software in all forms is also his sole hobby, whether playing PC games or tinkering with programming them. "I was playing Defender on the Commodore 64," he reminisces, "when I decided at the age of 12 or so that I want to be a computer programmer when I grow up."

Jon is presently employed as a senior .NET developer at a very well-known Internet services company whom you're more likely than not to have directly done business with. However, this blog and all of jondavis.net have no affiliation with, and are not representative of, his employer in any way.

Contact Me 


Amazon Collection

Most Recent of Many Library Investments

Tag cloud

Calendar

<<  September 2010  >>
MoTuWeThFrSaSu
303112345
6789101112
13141516171819
20212223242526
27282930123
45678910

View posts in large calendar