Question

I have a repository class that uses NPoco/PetaPoco to access data which reads from a common content table.

create table Content (
    Id int not null identity primary key,
    Type tinyint not null,
    ...
)

Then I have an abstract ContentBase class that other types inherit. The main difference between inherited types being the value of that type DB column value. They do have some additional columns per concrete content type, but that's not relevant here.

So. In order for my repository class to return any of the actual concrete classes I wanted to write a medhod:

public TContent Create<TContent>(string name, ...)
{
    ...
    // SP executes a few insers and returns newly created data instance
    return db.Single<TContent>(
        new Sql.Builder.Append(";exec dbo.CreateContent @Type, @Name, ...", new {
            Type = TContent.Type, // this is the problem
            Name = name,
            ...
        }));
}

As you can see I would require my base abstract class to define a static member to get the actual type value that should be accessible through generic type specification. And inheriting classes should set it according to their concrete type implementation.

The problem is of course that there's no such thing as abstract static members in C#.

How should I approach this problem in a way so that my repository will be able to provide Type value on its own without me providing it explicitly with the call? I would only like to provide generic class when calling it and get back correct concrete type.

Why does it have to be static member?

My method doesn't get an object instance of a particular type but it should create one. That's why I can't really have an instance member Type and read from that one while executing my repository method.

A possible start

As static members are shared among all instances and if this is base class all inheriting classes share the same member unless this class is generic. In that case we get a static member per generic type.

So I was thinking of adding an additional class between the base abstract and concrete classes:

public abstract class ContentBase
{
    ...
}

public abstract class ContentBase<TConcrete>
    where TConcrete: ContentBase<ContentBase> // is this constraint ok?
{
    public static ContentType Type = ???;
}

and then concrete classes:

public class ContentOne : ContentBase<ContentOne>
{
    ???
}

And as said I should be able to call my repository method as:

repo.Create<ContentOne>(name, ...)

where within this method repository should be able to access static member of generic type provided by the call...

Was it helpful?

Solution

Even with your idea of having an abstract ContentBase class, you won't be able to access a custom behavior for each derived class; here is a small test i tried to see if your idea could be used:

public abstract class ContentBase<T>
{
    public static Func<string> TypeLocator { get; set; }
    static ContentBase()
    {
        TypeLocator = () => typeof(T).Name;
    }

}

public class Content1 : ContentBase<Content1>    {
    private static string Content1Type = "The type of Content 1";
    static Content1()
    {
        TypeLocator = () => Content1Type;
    }
}

public class Content2 : ContentBase<Content2>    {}

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Content1.Type => " + GetObject<Content1>()); // Content1
        Console.WriteLine("Content2.Type => " + GetObject<Content2>()); // Content2
    }

    private static string GetObject<TContent>() where TContent: ContentBase<TContent>
    {
        var typeLocator = typeof(ContentBase<TContent>).GetProperties()[0].GetValue(null, null) as Func<string>;
        return typeLocator.Invoke();
    }
}

The best you can get is the type of the TContent, which may be enough for you to explore the mapping proposition below.

I don't really see a way to implement abstract static members, but you could resort to an external registering dictionary living as a singleton throughout your program which would be used as the mapper between your Type and your Type.DBType

public TContent Create<TContent>(string name, ...)
{
    ...
    // SP executes a few insers and returns newly created data instance
    return db.Single<TContent>(
        new Sql.Builder.Append(";exec dbo.CreateContent @Type, @Name, ...", new {
            Type = RegisteredTypes[typeof TContent],
            Name = name,
            ...
        }));
}

OTHER TIPS

Unfortunately the answer of "You Can't!" seems to have a concrete reason with no obvious workaround until C# 8 where it is directly supported.

Static members of non-static classes don't really belong to the same class as their non-static counterparts. The static parts really belong to a completely different class (a static class) whose name is made up by, and only known to, the compiler.

The slight-of-hand that the compiler plays to hide this fools you into thinking that it should be possible. If the compiler didn't hide the fact that there really is no such thing as a class with both static and non-static members, it would be more obvious that the name of the static class is unknown to the non-static class in the same way that references to any other classes referenced by your class are not directly available.

As of C#-8 you can define static methods (and therefore properties but not fields) in an interface so the problem is completely solved if you are not trapped behind a legacy barrier.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top