views:

516

answers:

5

As a novice to OOP, I am trying to implement an interface method with a base parameter by passing a (needed) subclass parameter. I have:


public interface IArticleDataAccess { int SaveArticle(NewsArticle thisArticle); }

public AnalysisDataAccess : IArticleDataAccess {
  public int SaveArticle(AnalysisArticle thisArticle) {
    // Specific save code that needs properties of AnalysisArticle not found in NewsArticle.
}
public class AnalysisArticle : NewsArticle {
  IArticleDataAccess dataAccess = new ArchivedArticleDataAccess();
  int Save() {
    return dataAccess.SaveArticle(this);
  }
}

The error is "ArchivedArticleDataAccess' does not implement interface member 'IArticleDataAccess.SaveArticle(NewsArticle)'" as the parameter types are not the same.

Am I making a small mistake or missing a fundemental OOP concept? Is there a pattern I can use to do this? Casting or Generics? Or is this a limitation of C# (no contravariant parameter support)?

+6  A: 

You can't apply contravariance here since it would break contract. I could send any kind of NewsArticle to the SaveArticle class through the interface, and then your AnalysisDataAccess would barf...

What you should do is use generics, like this:

public interface IArticleDataAccess<T> where T : NewsArticle
{
    int SaveArticle(T thisArticle);
}

public AnalysisDataAccess : IArticleDataAccess<AnalysisArticle> {
  public int SaveArticle(AnalysisArticle thisArticle) {
    // Specific save code that needs properties of AnalysisArticle not found in NewsArticle.
}
public class AnalysisArticle : NewsArticle {
  IArticleDataAccess<AnalysisArticle> dataAccess = new AnalysisArticleDataAccess();
  int Save() {
    return dataAccess.SaveArticle(this);
  }
}
Martinho Fernandes
A: 

You are getting a compile-time error because ArchivedArticleDataAccess must implement IArticleDataAccess and its declaration needs to look like this:

public class ArchivedArticleDataAccess : IArticleDataAccess 
{
    public int SaveArticle(NewsArticle thisArticle) 
    {
        // Save the article and return some integer
    }
}

and not:

public class ArchivedArticleDataAccess : IArticleDataAccess 
{
    public int SaveArticle(AnalysisArticle thisArticle) 
    {
        // Save the article and return some integer
    }
}
Darin Dimitrov
+2  A: 

You could do something like this. Explicitly implement the interface, make sure the argument is the proper type and call public method that handles the type you actually want. Just make sure you know what you want to do in case the NewArticle passed is not an AnalysisArticle. Additionally, think through this design and be certain you want and need to use AnalysisDataAccess as an IArticleDataAccess.

IArticleDataAccess int SaveArticle(NewsArticle article)
{
    AnalysisArticle analysisArticle = article as AnalysisArticle;
    if (analysisArticle != null)
            SaveArticle(analysisArticle);
    //else handle error or another routine
}

public int SaveArticle(AnalysisArticle thisArticle)
{
     //freely user analysis article members
}
Timothy Carter
A: 

You can't change the type of a method you implement on an interface, you need to change it so you cast the parameter to the right type inside the method implementation like this:

public AnalysisDataAccess : IArticleDataAccess {
  public int SaveArticle(NewsArticle thisArticle) {
    AnalysisArticle theArticle = thisArticle as AnalysisArticle;
    if (theArticle != null) {
    // Specific save code that needs properties of AnalysisArticle not found in

NewsArticle. } }

This should work.

Franklin Munoz
A: 

The use of generics looks elegant. But in trying to implement it, I don't actually use:


IArticleDataAccess<AnalysisArticle> dataAccess = new AnalysisArticleDataAccess();
//But instead have a DataAccess property in NewsArticle.  Once defined as:
IArticleDataAccess DataAccess { get; set; }
//If I change to:
IArticleDataAccess<NewsArticle> DataAccess { get; set; }
// It can't be implicitly set to type IArticleDataAccess<AnalysisArticle>.

Do I need to also generic to NewsArticle<T>? The self-referencing generic looks scary and requires a lot of changes. Better to just have each subclass reimplement the DataAccess property with the right type? Or am I missing something?