0

I have these classes (just an example):

/* Data classes */

public class Data
{
    public int Id { get; set; }
    ...
}

public InfoData<TInfo> : Data
    where TInfo: InfoBase
{
    public TInfo Info { get; set; }
    ...
}

/* Info classes */

// ABSTRACT
public abstract class InfoBase
{
    public int Id { get; set; }
    ...
}

public class ExtraInfo : InfoBase
{
    public IList<InfoBase> Related { get; set; }
}

// + other InfoBase inherited types

Then I have a method that consumes Data (or any inherited type instance) but has to read additional properties of actual object instance being passed as parameter:

TData Add<TData>(TData data)
    where TData: Data
{
    TData result = Get(
        data.Id,
        ...
        // how do I get to this one?
        data.Related[0].Id
    );
}

I can get to all Data class properties, but how do I get to additional properties of the actual instance because I can't just cast to something like

data as InfoData<InfoBase>

because InfoBase is abstract and I also can't change abstract generic with some inherited non-abstract type because there would be too many to consider...

Questions

  1. How can I get to Related property if few inherited classes have it?
  2. Is there a better way to implement my method?
Robert Koritnik
  • 103,639
  • 52
  • 277
  • 404
  • I think this post answers your question: http://stackoverflow.com/questions/729527/is-it-possible-to-assign-a-base-class-object-to-a-derived-class-reference-with-a – PrzemG Dec 17 '14 at 15:21
  • @PrzemG not exactly the answer as they're casting incompatible types. In my case I would like to access **actual** instance properties not something like accessing `Length` of type `object`... but I can see why the same applies in my case as it doesn't really matter whether types are compatible or not. It just won't work. – Robert Koritnik Dec 18 '14 at 07:14
  • @PrzemG: ...but I can see why the same applies in my case as it doesn't really matter whether types are compatible or not. Casting to derived types just won't work. – Robert Koritnik Dec 18 '14 at 07:20

1 Answers1

0

There should be no problem in casting to abstract, you can also add Interface definition and have your classes inherit them for example:

public interface IRelated{
    IList<InfoBase>Related {get; set;}
}

public class ExtraInfo : InfoBase, IRelated
{
    public IList<InfoBase> Related { get; set; }
}

public class OtherInfo:IRelated{
    public IList<InfoBase> Related { get; set; }
}

and cast to interface instead class:

ExtraInfo i = new ExtraInfo(){ Id = 5 };
var p = (InfoBase)i;
var q = (IRelated)i;
q.Related.Add(new ExtraInfo());

However I can see other problem in your code: in the Add method you mistype properties:

data.Related.Id

since data.Related returns IList of InfoBase not the InfoBase class itself.

Edit:

You have to be more clear what do you expect to get from the related (or what is Get parameter list).

Edit 2:

here is the Add method code for you:

TData Add<TData>(TData data) where TData:Data
{
    var infoBase = data as InfoBase; //or IRelated as in my earlier samples
    TData result;
    if (infoBase != null)
    {
        result = Get(data.id, infoBase.Related[0].Id); //you can also use infoBase.Id as first param
    }
    else
    {
        result = Get(data.id, null); //whatever you need to pass to the method if there is no related item
    }
}

Edit 3:

here's working code:

public class Data
{
    public int Id { get; set; }
    //...
}

public class InfoData<TInfo> : Data
    where TInfo: InfoBase
{
    public TInfo Info { get; set; }
    //...
}

/* Info classes */

// ABSTRACT
public abstract class InfoBase
{
    public int Id { get; set; }
    //  ...
}

public interface IRelated
{
    IList<InfoBase> Related {get; set;}
}

public class ExtraInfo : InfoBase, IRelated
{
    public IList<InfoBase> Related { get; set; }
}

public class OtherInfo : InfoBase, IRelated
{
    public IList<InfoBase> Related { get; set; }
}

...

class MainClass
{
    public static TData Get<TData>(int id, int? related)
        where TData: Data
    {
        Console.WriteLine("id: {0}, related:{1}", id, related);
        return (TData)null;
    }

    public static TData Add<TData>(TData data)
        where TData: Data
    {
        return Get<TData>(data.Id,null);
    }

    public static TData Add<TData, TInfo>(TData data)
        where TData: InfoData<TInfo>
        where TInfo: InfoBase
    {
        var infoBase = data.Info as IRelated;

        if (infoBase == null)
            return Get<TData>(data.Id, null);

        return Get<TData>(data.Id, infoBase.Related[0].Id);
    }

    public static void Main(string[] args) {
        var i = new ExtraInfo {
            Id = 5,
            Related = new List<InfoBase> { new ExtraInfo { Id = 1 }}
        };

        var data = new InfoData<ExtraInfo> { Id = 100, Info = i };
        var result1 = Add<InfoData<ExtraInfo>, ExtraInfo>(data);
        var result2 = Add(data);
    }
}

but... this whole code does not look good :)

There are couple possible improvements:

  • you can move Add method into the class Data and then override
  • you can pass related Id into the add method instead of passing the whole object
  • you can re-think the domain and check if you really need so much overloads.
Robert Koritnik
  • 103,639
  • 52
  • 277
  • 404
cyberhubert
  • 203
  • 1
  • 9
  • That was just a typo yes... Corrected. but apart from what you've written can you provide an example by re-writing my `Add` method to incorporate your supposed solution? – Robert Koritnik Dec 17 '14 at 16:30
  • This isn't correct. You're casting `TData` which may be either `Data` or `InfoData` to `InfoBase` directly. This is already a compile time error... It's not as simple as you might've though. If it was this simple I wouldn't be asking a question here. – Robert Koritnik Dec 18 '14 at 07:08
  • yes, you are right, it was my mistake. Please see my latest edit – cyberhubert Dec 18 '14 at 09:20
  • Well your code is close. You've introduced `IRelated` interface but you haven't used it in methods. Actually if you would use it in second `Add` it would simply cover all `InfoBase` inherited classes that also implement this specific interface. But I figured this has to be done as per type anyway. I'm currently doing something similar to your first alinee suggestion but instead of putting it directly on `Data` as I'm not using *active record* pattern I've rather written extension methods that get my first related `Id`. – Robert Koritnik Dec 18 '14 at 09:32
  • I've created a [fiddle](https://dotnetfiddle.net/aq325B) that uses `IRelated` generic type constraint hence `Add` method can be used with `ExtraInfo` as well as `OtherInfo` object instances why also eliminating few lines of code that try to read `infoBase`. – Robert Koritnik Dec 18 '14 at 10:47
  • brilliant tool, never heard about it :) I have created fork with using Reflection - simplifies things a bit: https://dotnetfiddle.net/YP17UR – cyberhubert Dec 18 '14 at 11:49
  • Hopefully you know about [JSFiddle](http://jsfiddle.net/) and [SQLFiddle](http://sqlfiddle.com/) as well. Helps a lot to show someone's code to others in working/breaking state. Just so you're informed. :) – Robert Koritnik Dec 18 '14 at 11:53
  • Your solution although plausible is not a good solution for two reasons: 1. it's slow due to reflection and 2. beats the purpose of generics. But it is a solution after all. – Robert Koritnik Dec 18 '14 at 11:55
  • added another one with more interface usage. Still not pretty, but seems to be more clear than others: https://dotnetfiddle.net/ojGPqe – cyberhubert Dec 18 '14 at 12:04