Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I organize C# classes that inherit from one another, but also have properties that inherit from one another?

I have an application that has a concept of a Venue, a place where events happen. A Venue has many VenueParts. So, it looks like this:

public abstract class Venue
{
    public int Id { get; set; }
    public string Name { get; set; }
    public virtual ICollection<VenuePart> VenueParts { get; set; }
}

A Venue can be a GolfCourseVenue, which is a Venue that has a Slope and a specific kind of VenuePart called a HoleVenuePart:

public class GolfCourseVenue : Venue
{
    public string Slope { get; set; }
    public virtual ICollection<HoleVenuePart> Holes { get; set; }
}

In the future, there may also be other kinds of Venues that all inherit from Venue. They might add their own fields, and will always have VenueParts of their own specific type.

Here are the VenuePart classes:

public abstract class VenuePart
{
    public int Id { get; set; }
    public string Name { get; set; }
    public abstract string NameDescriptor { get; }
}

public class HoleVenuePart : VenuePart
{
    public override string NameDescriptor { get { return "Hole"; } }
    public int Yardage { get; set; }
}

My declarations above seem wrong, because now I have a GolfCourseVenue with two collections, when really it should just have the one. I can't override it, because the type is different, right? When I run reports, I would like to refer to the classes generically, where I just spit out Venues and VenueParts. But, when I render forms and such, I would like to be specific.

I have a lot of relationships like this and am wondering what I am doing wrong. For example, I have an Order that has OrderItems, but also specific kinds of Orders that have specific kinds of OrderItems.

Update: I should note that these classes are Entity Framework Code-First entities. I was hoping this wouldn't matter, but I guess it might. I need to structure the classes in a way that Code-First can properly create tables. It doesn't look like Code-First can handle generics. Sorry this implementation detail is getting in the way of an elegant solution :/

Update 2: Someone linked to a search that pointed at Covariance and Contravariance, which seemed to be a way to constrain lists within subtypes to be of a given subtype themselves. That seems really promising, but the person deleted their answer! Does anyone have any information on how I may leverage these concepts?

Update 3: Removed the navigation properties that were in child objects, because it was confusing people and not helping to describe the problem.

like image 395
Chris Avatar asked Mar 27 '12 17:03

Chris


People also ask

What are the two main ways a file can be organized in C?

Writing to a file (fprintf or fputs)

What is file structure C?

A FILE is a type of structure typedef as FILE. It is considered as opaque data type as its implementation is hidden. We don't know what constitutes the type, we only use pointer to the type and library knows the internal of the type and can use the data. Definition of FILE is in stdio although it is system specific.


3 Answers

Here's one possible option using generics:

public abstract class VenuePart
{
    public abstract string NameDescriptor { get; }
}

public class HoleVenuePart : VenuePart
{
    public string NameDescriptor { get{return "I'm a hole venue"; } }
}

public class Venue<T> where T : VenuePart
{
    public int Id { get; set; }
    public string Name { get; set; }
    public virtual Company Company { get; set; }
    public virtual ICollection<T> VenueParts { get; set; }
}

public class GolfCourseVenue : Venue<HoleVenuePart>
{
}

Here GolfCourseVenue has the collection VenueParts, which can contain HoleVenueParts or super classes HoleVenueParts. Other specializations of Venue would restrict VenueParts to containing VenueParts specific to that venue.

A second possibility is pretty much as you had it

public abstract class VenuePart
{
    public abstract string NameDescriptor { get; }
}

public class HoleVenuePart : VenuePart
{
    public string NameDescriptor { get{return "I'm a hole venue"; } }
}

public class Venue 
{
    public int Id { get; set; }
    public string Name { get; set; }
    public virtual Company Company { get; set; }
    public virtual ICollection<VenuePart> VenueParts { get; set; }
}

public class GolfCourseVenue : Venue
{
}

Now GolfCourseVenue has the collection VenueParts, which can contain VenueParts or super classes VenueParts. Here all specializations of Venue can contain any type of VenuePart which may or may not be appropriate.

In answer to your comment about covariance, I would propose something like this:

public abstract class VenuePart
{
    public abstract string NameDescriptor { get; }
}

public class HoleVenuePart : VenuePart
{
    public override string NameDescriptor { get{return "I'm a hole venue"; } }
}

public abstract class Venue 
{
    public int Id { get; set; }
    public string Name { get; set; }
    public abstract ICollection<VenuePart> VenueParts { get; }
}

public class GolfCourseVenue : Venue
{
    private ICollection<HoleVenuePart> _holeVenueParts;

    public GolfCourseVenue(ICollection<HoleVenuePart> parts)
    {
       _holeVenueParts = parts;
    }

    public override ICollection<VenuePart> VenueParts 
    { 
        get
        { 
            // Here we need to prevent clients adding
            // new VenuePart to the VenueParts collection. 
            // They have to use Add(HoleVenuePart part).
            // Unfortunately only interfaces are covariant not types.
            return new ReadOnlyCollection<VenuePart>(
                   _holeVenueParts.OfType<VenuePart>().ToList()); 
        } 
    }

    public void Add(HoleVenuePart part) { _holeVenueParts.Add(part); }
}
like image 169
Phil Avatar answered Oct 05 '22 05:10

Phil


I look forward to the advice of others - but my approach is to use generics in this case. With generics, your GolfCourseVenue's "parts" are strong typed!

...and as I type this everyone else is saying generics too. HOW DO YOU overstackers type so dang fast?!

Anyways, pretending I'm still first -

public class VenuePart
{
}

public class HoleVenuePart : VenuePart
{
}

public abstract class Venue<T> where T : VenuePart
{
  public int Id { get; set; }
  public string Name { get; set; }
  public virtual Company Company { get; set; }
  public virtual ICollection<T> Parts { get; set; }
}

public class GolfCourseVenue : Venue<HoleVenuePart>
{
  public string Slope { get; set; }
}

Also, as a 2nd option, you could use an interface too, so in case you didn't like the name Parts, you could call it Holes when the derived type is known to be a GolfCourse

public class VenuePart
{
}

public class HoleVenuePart : VenuePart
{
}

public interface IPartCollection<T> where T : VenuePart
{
  ICollection<T> Parts { get; set; }
}

public abstract class Venue<T> : IPartCollection<T> where T : VenuePart
{
  public int Id { get; set; }
  public string Name { get; set; }
  public virtual Company Company { get; set; }
  public virtual ICollection<T> Parts { get; set; }
}

public class GolfCourseVenue : Venue<HoleVenuePart>
{
  public string Slope { get; set; }
  ICollection<HoleVenuePart> IPartCollection<HoleVenuePart>.Parts { get { return base.Parts; } set { base.Parts = value; }}

  public virtual ICollection<HoleVenuePart> Holes { get { return base.Parts; } set { base.Parts = value;}}
}
like image 21
payo Avatar answered Oct 05 '22 05:10

payo


You can use Covariance

public abstract class Venue
{
    public int Id { get; set; }
    public string Name { get; set; }
    public virtual Company Company { get; set; }
    public virtual IEnumerable<VenuePart> VenueParts { get; set; }
}

public class GolfCourseVenue : Venue
{
    public string Slope { get; set; }

    public  GolfCourseVenue()
    {
        List<HoleVenuePart> HoleVenueParts = new List<HoleVenuePart>();
        HoleVenueParts.Add(new HoleVenuePart());
        VenueParts = HoleVenueParts;
    }
}

Assuming HoleVenuePart is inherited from VenuePart

like image 25
Raj Ranjhan Avatar answered Oct 05 '22 07:10

Raj Ranjhan