0

I have an overloaded radio button extension method that is throwing the runtime execution off. The arguments of the first method can, in certain cases, be confused for the arguments of the second, and vice-versa.

public static MvcHtmlString MyRadioButton<TModel>( this HtmlHelper<TModel> helper, string property_name, string value, bool is_checked, string label = "", object attributes = null, bool separate_label = false, bool within_div = true, bool label_after = true )
        {
            // method implementation
        }

and the overload:

public static MvcHtmlString MyRadioButton<TModel>( this HtmlHelper<TModel> helper, string property_name, string value, bool is_checked, object attributes = null, bool separate_label = false, bool within_div = true, bool label_after = true )
        {
            return MyRadioButton(helper, property_name, value, is_checked, "", attributes, separate_label, within_div, label_after);
        }

In this case:

Html.MyRadioButton("name", "value", true, "");

the empty string is valid as both the object-type "attributes" argument and as the string-type "label" argument, causing a conflict between the methods.

Is there a way to generically type an argument and then exclude types from that definition in the method? I'm thinking maybe an inheritance class of the object type that accepts a list of types to exclude, like object<string>. The alternative solution is to jumble the arguments of the overload so it is apparent which needs to be called, but that's far less elegant than properly typing the arguments.

Luke Faez
  • 13
  • 4
  • I think I may be looking for type constraints. I'll continue looking into this and answer the question if it turns out to solve the problem. – Luke Faez May 11 '20 at 16:09
  • I'm not seeing how to get where I'm going with where T clauses, as there's no way to exclude types, from my searching. I may end up just using an AnonymousType for the parameters argument, since it is generally passed as `new { param: value }` – Luke Faez May 11 '20 at 16:22
  • seems that anonymously-typed arguments aren't possible in C# https://stackoverflow.com/questions/6624811/how-to-pass-anonymous-types-as-parameters – Luke Faez May 11 '20 at 16:30
  • @AlexeiLevenkov Beginning with "label" in the first and "attributes" in the second, each argument has a default value, so if no further args are supplied to the overload beyond "label", it can potentially pass the intended label as the "attributes" object in the overload. – Luke Faez May 11 '20 at 16:34
  • Anonymous typed parameters are **certainly** possible C#. [Dapper](https://github.com/StackExchange/Dapper), for example, uses them to create `SqlCommand` parameters. The challenge is controlling the shape of the anonymous type. – Connor Low May 11 '20 at 23:31
  • @ConnorLow I'm aware of object-typed arguments, but have not yet seen how an object structured `new { foo: bar }` can be required through a method. – Luke Faez May 12 '20 at 20:04

4 Answers4

1

Since the empty string could be either a string or an object you can distinguish whether you want the empty string to be passed as the label or the attributes when you call by specifying the name:

Html.MyRadioButton("name", "value", true, label: "")
Html.MyRadioButton("name", "value", true, attributes: "")
Wyck
  • 10,311
  • 6
  • 39
  • 60
  • Can't display my +1 yet, but I think this is the winning answer. No refactoring needed for type-specificity since this allows name-specificity to dominate. My question is perhaps only answerable as yes/no, with no being the answer given the circumstances of C#, so I'll let the proposal most **helpful**, in my opinion, have it. Thanks for the solve! – Luke Faez May 12 '20 at 20:11
0

Maybe a simple solution like creating a special class of string can do the job better than using generic type constraint

[UPDATED]
Here is my suggestion:

public class MySpecialString
{
    public string MyStringl;
    public MySpecialString(string txt)
    {
        this.MyStringl = txt;
    }
}

public class MyAttributes
{
    public MyAttributes()
    {

    }
}

public static class Extentions
{
    public static MvcHtmlString MyRadioButton<TModel>(this HtmlHelper<TModel> helper, string property_name,
        string value, bool is_checked, MySpecialString mystring = null , object attributes = null, bool separate_label = false,
        bool within_div = true, bool label_after = true)
    {
        // method implementation
    }

    public static MvcHtmlString MyRadioButton<TModel>(this HtmlHelper<TModel> helper, string property_name,
        string value, bool is_checked, MyAttributes attributes = null, bool separate_label = false,
        bool within_div = true, bool label_after = true)
    {
        // method implementation
    }
}
Jonathan Applebaum
  • 5,738
  • 4
  • 33
  • 52
0

Why the overload at all? Simply delegate inside one single method to the appropiate implementation defining a default value for label that allows you to discern what option the consumer wants.

public static MvcHtmlString MyRadioButton<TModel>(this HtmlHelper<TModel> helper,
                                                  string property_name,
                                                  string value,
                                                  bool is_checked,
                                                  string label = null, //general case
                                                  object attributes = null,
                                                  bool separate_label = false,
                                                  bool within_div = true,
                                                  bool label_after = true )
{
     if (label == null)
         return myImplentationWithLabelNotDefined( //all arguments//);

     return myImplementationWithLabelDefined(//all arguments//);
}

You can even define the helper methods as inner functions to avoid argument clutter...

InBetween
  • 32,319
  • 3
  • 50
  • 90
  • The overload is meant to overcome the need to define "label" when no label is needed, as there are many cases in which you want to pass "attributes" but no "label". This is an imitation of the overloads present in the default Html RadioButton static method, so not something I came up with myself, unfortunately! haha – Luke Faez May 11 '20 at 17:01
  • @LukeFaez Well, if its common you need attributes but not label, then switch those two around... – InBetween May 11 '20 at 18:24
0

If the attributes parameter is meant to be used for POCOs and not value types (e.g. never string, integer, boolean, etc.), you could do something like this:

public static MvcHtmlString MyRadioButton<TModel, T>( 
    this this HtmlHelper<TModel>, ...,
    T attributes,
    ... // omitted for brevity
) where T: new() // <- IMPORTANT LINE
{ 
    // etc...
}

// which can be used with any anonymous or structured object:
helper.MyRadioButton(new { attribute1 = "hello world" }); 
helper.MyRadioButton(new MyClass());

// but not value types:
helper.MyRadioButton("this won't work for the above overload");

I would hope adding a generic constraint would be enough for the runtime to "know better" if this matches your use-case.


If you are only using this for POCOs, I would add the constraint either way. Then you never have to worry about accidentally passing in a value type.

UPDATE

Is there a way to generically type an argument and then exclude types from that definition in the constructor?

I dodged this question in my original answer. Using the where constraint, you can restrict passed types to a type, but you cannot specify types to exclude. See Type parameter constraints language specification and documentation for more details.

Connor Low
  • 5,900
  • 3
  • 31
  • 52
  • I considered type constraints soon after posting and found them inadequate for purpose, but thanks for the input! – Luke Faez May 12 '20 at 19:58