M.R.
M.R.

Reputation: 4827

Polymorphism on c# property

I have a class like this:

public class ItemField
{

    public string FieldName { get; set; }

    public object FieldValue
    {
        get;
        set;
    }
}

I want to have it so that "FieldValue" can only be of type string, MyItem, or List<MyItem>. I also want it so that when code consumes this class, accessing "FieldValue" returns the right type of object. Is there a way to do this? I'm not too clear on generics, but there seems to be a way to do this using that...

Upvotes: 1

Views: 430

Answers (5)

cuongle
cuongle

Reputation: 75306

As you know string is not used as generic constraint, take a look on MSDN:

public sealed class String : IComparable, ICloneable, IConvertible, IComparable<string>, IEnumerable<char>, IEnumerable, IEquatable<string>

So you can work around like this if you to constraint T as string

public class ItemField<T> where T :  ICloneable, IConvertible, IComparable<string>, IEnumerable<char>, IEnumerable, IEquatable<string>
{
    public string FieldName { get; set; }
    public T FieldValue { get; set; }

}

Upvotes: 1

Servy
Servy

Reputation: 203821

So it seems that you're not sure if you should be returning one item or a collection of items. Rather than changing the return type dynamically (which is something you can't really do in a useful way) it would make more sense to just always return a collection. That collection can always contain a single item if that's appropriate.

If you change the return type to List<MyItem> then most of the problems and bad code smell just go away.

Upvotes: 1

Sergey Kalinichenko
Sergey Kalinichenko

Reputation: 726489

Assuming that the client knows exactly what type of the field it gets, you can change the property definition into a function to use generics, like this:

private object fieldValue;

public T GetFieldValue<T>() {
    return (T)fieldValue;
}

The users must call this method like this:

MyItem item = itemField.GetFieldValue<MyItem>();

However, the cast is still there, and it is not clear how the users are expected to know what type to supply as the generic type parameter.

A better approach would be to use some sort of a double dispatch mechanism, for example, the visitor pattern, to "invert" the direction and let your field "push" the value into the caller.

interface IItemFieldVisitor {
    void VisitString(string val);
    void VisitMyItem(MyItem val);
    void VisitList(IList<MyItem> val);
}

interface IItemField {
    string Name {get;set;}
    void Accept(IItemFieldVisitor visitor);
}

class StringItemField : IItemField {
    public string Name {get;set;}
    private string val;
    public void Accept(IItemFieldVisitor visitor) {
        visitor.VisitString(val);
    }
}

class MyItemItemField : IItemField {
    public string Name {get;set;}
    private MyItem val;
    public void Accept(IItemFieldVisitor visitor) {
        visitor.VisitMyItem(val);
    }
}

class MyItemListItemField : IItemField {
    public string Name {get;set;}
    private IList<MyItem> val;
    public void Accept(IItemFieldVisitor visitor) {
        visitor.VisitList(val);
    }
}

Now you can create instances of IItemFieldVisitor interface, and pass them to IItemField objects. The visitors will receive callbacks with specific values without knowing the type of the IItemField objects being visited.

Upvotes: 2

hagensoft
hagensoft

Reputation: 1497

you could create your own abstract class 'Field' and have a string, MyItem or List derive from the abstract class. http://www.techotopia.com/index.php/Understanding_C_Sharp_Abstract_Classes

you can look into abstract class factory at this link: http://dotnetslackers.com/articles/designpatterns/Design-Patterns-Part-3.aspx

Upvotes: 1

Guffa
Guffa

Reputation: 700212

A property can only return one specific type, you can't make it return different data types depending on what the actual type of the field value is.

You can use generics for the class, but then you need to create the class instance for the type of data that you want to store in it:

public class ItemField<T> {

  public string FieldName { get; set; }

  public T FieldValue {
    get;
    set;
  }

}

Upvotes: 2

Related Questions