简体   繁体   中英

C#: Assign child class instance to interface implemented by abstract class

public interface IParser<T> where T: new()
{
    IList<T> Parse();
}

Above interface is implemented by following abstract class

public abstract class BaseParser<T>: IParser<T> where T : new()
{
   protected abstract string Sql { get;}
   public List<T> Parse()
   {
      // do parsing
      Console.WriteLine(Sql);
   }
}

Following are two concrete implementation of above abstract class

public class EMailParser: BaseParser<Email>
{
    protected override string Sql
    {
        get
        {
            return @"SELECT * FROM emails";
        }
    }
}

public class UrlParser : BaseParser<Url>
{
    protected override string Sql
    {
        get
        {
            return @"SELECT * From Url";
        }
    }
}

Usage:

class Program
{
    static void Main(string[] args)
    {
       if(args[1] == "url")
          Parser<Url>();
       else
          Parser<Email>();
    }
    static void Parse<T>()
    {
       // Create instance based on typof T and then assign to implementaion
       IParser<T> parser = typeof(T) == typeof(Url) ? new UrlParser(): new EmailParser();
       parser.Parse();
    }
}

I want to create instance of EmailParser or UrlParser base on generic type provided in Program.Main method and assign it to interface implemented by BaseParser (abstract class). How can I do this? I know i can solve this problem by modifying Program.Parse<T> as following

static void Parse<T>() where T: new()
{
    IParser<T> parser = typeof(T) == typeof(Url) ? new UrlParser() as BaseParser<T> : new EmailParser() as BaseParser<T>;
    parser.Parse();
}

However I want to know why I can't assign child class instance to interface implemented by abstract class??

I can't understand why following line does not work

IParser<T> parser = typeof(T) == typeof(Url) ? new UrlParser(): new EmailParser();

and why this line work

IParser<T> parser = typeof(T) == typeof(Url) ? new UrlParser() as BaseParser<T> : new EmailParser() as BaseParser<T>;

As per @nawfal answer this line also should not work because BaseParser and BaseParser are different types. Does there exists implicit case for IParser from BaseParser?

I believe the issue is that the compiler doesn't consider the type to which you are assigning the result of the ?: conditional when parsing the conditional. Rather, the ?: is parsed in isolation, so the compiler can't figure out which type to use.

Edit: From section 7.14 of the C# 5.0 specification:

The second and third operands, x and y, of the ?: operator control the type of the conditional expression. If x has type X and y has type Y then:

  • If an implicit conversion (§6.1) exists from X to Y, but not from Y to X, then Y is the type of the conditional expression.

  • If an implicit conversion (§6.1) exists from Y to X, but not from X to Y, then X is the type of the conditional expression.

  • Otherwise, no expression type can be determined, and a compile-time error occurs.

Because there is no implicit conversion between UrlParser and EmailParser . They both go back to BaseParser<Url> and BaseParser<Email> (or IParser<Url> and IParser<Email> ) respectively, both of which are different types as far as compiler is concerned.

I would keep a dictionary to hold a map of type information and then use reflection. Something like (not tested):

static Dictionary<string, Type> typeInfos = new Dictionary<string, Type> 
{
    { "url", typeof(Url) },
    { "email", typeof(Email) },
    // and so on
};

And you do,

class Program
{
    static void Main(string[] args)
    {
        Parse(args[1]);
    }

    static void Parse(string type)
    {
        var parserType = typeof(BaseParser<>)
            .Assembly // or whatever the assembly is
            .GetTypes()
            .First(t => t.BaseType?.GetGenericArguments().FirstOrDefault() == typeInfos[type]);
        dynamic parser = Activator.CreateInstance(parserType);
        parser.Parse();
    }
}

Use expression trees and/or cache things to make things faster.

Update: No, BaseParser<T> and BaseParser<T> are the exact same thing. T can only have one value at a time in your generic method. The real question is how can you cast new UrlParser() as BaseParser<T> . In a generic context it is possible to cast anything to anything using as C# rules are bit liberal there (I dont know the which part exactly in spec).

You can use a dynamic dispatch:

static IParser<Url> CreateParser(Url uri)
{
    return new UrlPaqrser<Url>();
}

static IParser<Email> CreateParser(Email mail)
{
    return new EmailPaqrser<Email>();
}

static void Main(string[] args)
{
    dynamic t = new Url();
    var parser = CreateParser(t); // invokes CreateParser(Url uri)

    t = new Email();
    parser = CreateParser(t);  // invokes CreateParser(Email email)
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM