質問

I have a collection of IEnumerable<School> that is being passed to an extension method that populates a DropDownList. I would also like to pass the DataValueField and DataTextField as an argument but I wanted them to be strongly typed.

Basically, I don't want to pass a string for the DataValueField and DataTextField arguments, it's error prone.

public static void populateDropDownList<T>(this DropDownList source,
        IEnumerable<T> dataSource,
        Func<T, string> dataValueField,
        Func<T, string> dataTextField) {
    source.DataValueField = dataValueField; //<-- this is wrong
    source.DataTextField = dataTextField; //<-- this is wrong
    source.DataSource = dataSource;
    source.DataBind();
}

Called like so...

myDropDownList.populateDropDownList(states,
        school => school.stateCode,
        school => school.stateName);

My question is, how can I pass the DataValueField and DataTextField strongly typed as an argument to populateDropDownList?

役に立ちましたか?

解決

Based off Jon's answer and this post, it gave me an idea. I passed the DataValueField and DataTextField as Expression<Func<TObject, TProperty>> to my extension method. I created a method that accepts that expression and returns the MemberInfo for that property. Then all I have to call is .Name and I've got my string.

Oh, and I changed the extension method name to populate, it was ugly.

public static void populate<TObject, TProperty>(
        this DropDownList source, 
        IEnumerable<TObject> dataSource, 
        Expression<Func<TObject, TProperty>> dataValueField, 
        Expression<Func<TObject, TProperty>> dataTextField) {
    source.DataValueField = getMemberInfo(dataValueField).Name;
    source.DataTextField = getMemberInfo(dataTextField).Name;
    source.DataSource = dataSource;
    source.DataBind();
}

private static MemberInfo getMemberInfo<TObject, TProperty>(Expression<Func<TObject, TProperty>> expression) {
    var member = expression.Body as MemberExpression;
    if(member != null) {
        return member.Member;
    }
    throw new ArgumentException("Member does not exist.");
}

Called like so...

myDropDownList.populate(states,
    school => school.stateCode,
    school => school.stateName);

他のヒント

If you're only trying to use property chains, you could change the parameter to Expression<Func<T, string>> and then extract the property names involved - you'll need to dissect the Expression<TDelegate> you get... you'd expect that the Body will be a MemberExpression representing a property access. If you've got more than one (school.address.FirstLine) then the target expression of one member access will be another one, etc.

From that, you can build up a string to use in the DataValueField (and the DataTextField). Of course, the caller can still screw you over:

myDropDownList.populateDropDownList(states,
    school => school.stateCode.GetHashCode().ToString(),
    school => school.stateName);

... but you can detect it and throw an exception, and you're still refactor-proof for good callers.

With what you were trying, even if you did get it to compile/run, it would still be wrong because the Value & Text fields would've been set to a value in the list instead of the property name (ie, DataValueField = "TX"; DataTextField = "Texas"; instead of DataValueField = "stateCode"; DataTextField = "stateName"; like you really want).

public static void populateDropDownList<T>(this DropDownList source,
        IEnumerable<T> dataSource,
        Func<string> dataValueField,
        Func<string> dataTextField) {
    source.DataValueField = dataValueField();
    source.DataTextField = dataTextField();
    source.DataSource = dataSource;
    source.DataBind();
}

myDropDownList.populateDropDownList(states,
        "stateCode",
        "stateName");
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top