DotnetDude
DotnetDude

Reputation: 11807

C# Generics function

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;
}

Upvotes: 13

Views: 21055

Answers (4)

RossFabricant
RossFabricant

Reputation: 12492

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.

Upvotes: 1

Reed Copsey
Reed Copsey

Reputation: 564413

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.

Upvotes: 3

tuinstoel
tuinstoel

Reputation: 7306

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";
    }
}

Upvotes: 0

Jon Skeet
Jon Skeet

Reputation: 1500515

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.)

Upvotes: 21

Related Questions