tags:

views:

2377

answers:

4

Can someone explain this behavior in Generics?

I have a generic function in C#

protected virtual void LoadFieldDataEditor <T> (ref T control, string strFieldName) where T : Control
{
  //T can be different types of controls inheriting from System.Web.UI.Control
  if (control is TextBox)
  {
   //This line gives an error
   //((TextBox)control).Text = "test";

   //This line works! 
   (control as TextBox).Text = "Test";
  }
}

On a side note, can I use switch case when I am doing a "Control is TextBox" type of checking?

EDIT:

Forgot to add the error message Sorry!

Here you go:

Error   3 Cannot convert type 'T' to 'TextBox'

EDIT:

While we are talking about generics, I have another question. (Wasn't sure If I had to start a new post)

The method has been expanded to include another generic type

protected virtual void LoadFieldDataEditor <T1, T2> (T1 control, T2 objData, string strFieldName) where T1 : Control where T2 : BaseDataType
{
  //I will need to access field1. 
  //I don't know at compile time if this would be SomeType1 or 
 //SomeType2 but all of them inherit from BaseDataType. 

  //Is this possible using generics?
}

public abstract class BaseDataType {}

public class SomeType1 : BaseDataType
{
   string field1;
   string field2;
}
+13  A: 

The rules for what a generic type can be converted to are quite tricky, and occasionally counterintuitive, as in this case. See section 6.2.6 of the C# spec for details. There are places where they could be laxer, and I think this is one of them. You can cast up to object and then down again, but that's ugly.

In this case the better solution would be:

protected virtual void LoadFieldDataEditor <T> (ref T control,
                                                string strFieldName) 
    where T : Control
{
    TextBox textBox = control as TextBox;
    if (textBox != null)
    {
        textBox.Text = "test";
    }
}

Aside from anything else, this only requires a single execution time check instead of two.

For the side note: no, you can't use switch/case on types. (You could get the type name and switch on that, but it would be horrible.)

Jon Skeet
You are creating a new textBox in this method. Will changing the text on this change the text on the one that was sent as a param (ref Control)?
DotnetDude
No, this method doesn't create a new TextBox. Which line do you think creates a new one? I strongly suspect you don't actually need to be passing control by ref...
Jon Skeet
I thought this line - TextBox textBox = control as TextBox;created a new textbox. I might have to brush up on my "pass by val" and pass by ref concepts. My understanding was when you pass by value (ie., without ref) it creates a new var in a new memory location and copies the source value
DotnetDude
Yes, it copies the source value - but that's just a reference, not the control itself. Definitely read http://pobox.com/~skeet/csharp/parameters.html and http://pobox.com/~skeet/csharp/references.html
Jon Skeet
I usually use the pattern of casting using the 'as' keyword and then checking for null when I have to check for a number of possible specialisations of a more generic class - IMHO it results in good protective coding.
Gordon Mackie JoanMiro
+1  A: 

The first line gives the compiler error: "Cannot convert type T to TextBox." That kind of cast is only legal if the compiler can know that it is possible to convert the starting class to the ending class. Because T could be anything, there's no way for the compiler to know. Even though you're checking at runtime this doesn't appease the compiler. The second kind of cast is OK, because it will just return null if the cast doesn't work. EDIT: As tuinstoel points out, the rules for casting are more complicated than I described.

RossFabricant
See my response.
tuinstoel
A: 

In response to @rossfabricant.

It is not so simple, the first method does compile, the second not.

void Test(Control control)
{
    if (control is TextBox)
    {
       ((TextBox)control).Text = "test";
    }
}

void Test<T>(T control) where T : Control
{
    if (control is TextBox)
    {
        ((TextBox)control).Text = "test";
    }
}
tuinstoel
+2  A: 

I'd highly recommend refactoring this to be:

protected virtual void LoadFieldDataEditor(Control control, string strFieldName)

As mentioned in a few comments, this method does not need generics at all.

Since you're constraining to a Control, you know the base class, and it's a reference type (Control), so you can avoid the generics and the ref parameter declaration.

Since Control is a reference type, you are free to change it's properties in the method, and this will work correctly. Setting .Text, etc, will do exactly what you are trying to do, but be much simpler.

There is a small chance that you could need it to be:

protected virtual void LoadFieldDataEditor(ref Control control, string strFieldName)

but this would only be required if you were going to reassign control inside your method (ie: control = new TextBox();). I would strongly recommend against doing that, as it can cause some very unexpected behavior and would not be obvious. If you are trying to create a new control, using an out parameter, or just returning the new control would make everything much more clear.

Also, in general, it's a good idea to avoid generic methods unless there is a good reason to include them. The FxCop team has added (and later removed) some rules trying to discourage their use because they tend to make code less understandable in the long run. There are many good reasons for using generic methods, but this doesn't require their use, so I would recommend avoiding them.

Reed Copsey
Thanks for the explanation. Assigning text was only for the purpose of asking the question. In reality, I have a lot of other stuff going on. But yes, I did refactor to not use ref and it works great! Thanks
DotnetDude