隐式运算符和类层次结构

时间:2012-07-12 13:28:03

标签: c#

我有以下类层次结构。

public abstract class ResourceBase { }

public abstract class WebResourceBase : ResourceBase  {
  public ResourceBase LocalPath { get; set; }
  public ResourceBase FtpPath { get; set; }
}

public class JavaScript : WebResourceBase { }

我想做的是有这样的声明。

new JavaScript() {
  LocalPath = "/path/goes/here/1.js",
  FtpPath = "ftp://path/goes/here/1.js"
}

这里显而易见的答案是使用隐式运算符,但问题是我想将派生类型分配给那些与声明类型相同的属性,因此LocalPathFtpPath将是类型为JavaScript

我希望我的解决方案比我现在的解决方案更灵活。这段代码让我的皮肤爬行。希望有一种方法可以使用反射,我尝试使用StackTrace类来查找信息,但没有运气。任何帮助,将不胜感激。感谢。

public abstract class ResourceBase { 
  public static implicit operator ResourceBase(string path) {
            if (path.EndsWith(".js"))
                return new JavaScript(path);
            // etc...
  }
}

4 个答案:

答案 0 :(得分:3)

这假设WebResourceBase实际上是为了继承ResourceBase

不幸的是,你将无法使隐式运算符更好看 - 泛型在这里不起作用。

<小时/> 替代方案:限制ResourceBase的泛型

现在我已经重新阅读并理解你所追求的内容,一种选择是修改你的类以包含一个引用派生类的泛型参数(有点像自引用):

public abstract class ResourceBase 
{ }

public abstract class WebResourceBase<T> : ResourceBase
    where T : WebResourceBase<T>
{
    public T LocalPath { get; set; }
    public T FtpPath { get; set; }
}

public class JavaScript : WebResourceBase<JavaScript> 
{ 
}

然后您会在JavaScript中看到属性LocalPathFtpPath现在也属于JavaScript类型。

现在,您的作业接受JavaScript类型:

new JavaScript() 
{
    LocalPath = new JavaScript("/path/goes/here/1.js"),
    FtpPath = new JavaScript("ftp://path/goes/here/1.js")
}

这种方法的好处是它将基本属性约束为当前类型或更多派生,而不是更少派生。

<小时/> 替代方法:显式解析而不是implicit运算符

如果您需要将LocalPathFtpPath变量保留为ResourceBase,或者在此处不能使用泛型,则隐式运算符将开始变得混乱。最好提供像静态方法一样的显式内容:

new JavaScript() 
{
    LocalPath = JavaScript.Parse("/path/goes/here/1.js"),
    FtpPath = JavaScript.Parse("ftp://path/goes/here/1.js")
}

class JavaScript
{
    public static ResourceBase Parse(string s)
    {
        if (path.EndsWith(".js"))
            return new JavaScript(path);

        throw new Exception();
    }
}

<小时/> 替代方法:类层次结构解析而不是implicit运算符

通过构造函数将字符串使用到类型中,并将属性设置为只读:

public abstract class ResourceBase
{ }

public abstract class WebResourceBase
{
    public ResourceBase LocalPath { get; private set; }
    public ResourceBase FtpPath { get; private set; }

    protected abstract ResourceBase ParseLocalPath(string s);
    protected abstract ResourceBase ParseFtpPath(string s);
}

public class JavaScript : WebResourceBase<JavaScript> 
{ 
    protected override ResourceBase ParseLocalPath(string s)
    {
        // etc.
    } 
    protected override ResourceBase ParseFtpPath(string s)
    {
        // etc.
    } 
}

<小时/> 说实话,大多数这似乎有点过分,只是为了从字符串中将两个属性设置为特定类型,你有很多选项 - 即使是隐式运算符也可以。

选择最容易理解的那个。除非你去挖掘它,否则运算符重载往往会有些隐藏。

答案 1 :(得分:1)

我也假设WebResourceBase应该从ResourceBase继承

在基类上有一个protected映射机制,派生类可以自己订阅:

public abstract class ResourceBase
{
    // Records how to make a ResourceBase from a string, 
    // on a per-extension basis
    private static Dictionary<string, Func<string, ResourceBase>> constructorMap
        = new Dictionary<string, Func<string, ResourceBase>>();

    // Allows a derived type to subscribe itself
    protected static void Subscribe(
        string extension, 
        Func<string, ResourceBase> ctor)
    {
        if (constructorMap.ContainsKey(extension))
            throw new Exception("nuh uh");

        constructorMap.Add(extension, ctor);
    }

    // Given a string, finds out who has signed up to deal with it,
    // and has them deal with it
    public static implicit operator ResourceBase(string s)
    {
        // Find a matching extension
        var matches = constructorMap.Where(kvp => s.EndsWith(kvp.Key)).ToList();

        switch (matches.Count)
        {
            case 0:
                throw new Exception(
                  string.Format("Don't know how to make {0} into a ResourceBase",
                                s));
            case 1:
                return matches.Single().Value(s);
            default:
                throw new Exception(string.Format(
                  "More than one possibility for making {0} into a ResourceBase",
                  s));
        }
    }
}

中间类型基本没有变化,但是在某些类型检查中我无法确定如何在编译时强制执行:

public abstract class WebResourceBase : ResourceBase
{
    private ResourceBase localPath;
    public ResourceBase LocalPath
    {
        get { return localPath; }
        set
        {
            if (value.GetType() != GetType())
            {
                throw new Exception("Naughty");
            }
            localPath = value;
        }
    }        

    private ResourceBase ftpPath;
    public ResourceBase FtpPath
    {
        get { return ftpPath; }
        set
        {
            if (value.GetType() != GetType())
            {
                throw new Exception("Naughty");
            }
            ftpPath = value;
        }
    }
}

具体类型如下:

public class JavaScript : WebResourceBase
{
    public JavaScript()
    {

    }
    private JavaScript(string s)
    {
    }

    static JavaScript()
    {
        Subscribe("js", s => (ResourceBase)new JavaScript(s));
    }
}

用法如您所指定:

        var js = new JavaScript
        {
            LocalPath = "hello.js",
            FtpPath = "hello.js"
        };

请注意,尽管ResourceBaseconstructorMap的签名中有Subscribe,但在上述语句LocalPathFtpPath 之后> JavaScript个对象。

答案 2 :(得分:0)

我只想在流程中添加一个额外的间接层(这对于设计问题而言,这是一个很好的答案,这真是令人惊讶;))。

我要么添加一个新的构造函数,它接受两个字符串参数,添加两个额外的字符串属性,或两者兼而有之。 (我假设您只是从这里添加两个新的字符串属性;您可以从那里进行推断。)

如果你添加一个新属性:JavaScriptLocalPath它可以在它的set方法中将字符串转换为特定于ResourceBase的派生类型JavaScript,并设置为{{1使用该结果的属性。我假设它的get方法也可以从LocationPath中提取该字符串(这样你也不需要费心存储ResourceBase)。

答案 3 :(得分:0)

这是我的想法:

public abstract class WebResourceBase {
  public ResourceBase LocalPath { get; set; }
  public ResourceBase FtpPath { get; set; }

  protected abstract ResourceBase ConvertFromString(string path);
  public string LocalPathStr { set { LocalPath = ConvertFromString(value); } }
  public string FtpPathStr { set { FtpPath = ConvertFromString(value); } }
}

可能会有所改进。