3

我有以下类层次结构。

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"
}

这里明显的答案是使用隐式运算符,但问题是我想为那些与声明的类型相同的属性分配派生类型,LocalPath并且FtpPath将是 type JavaScript

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

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

4 回答 4

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运算符

如果您需要将LocalPathandFtpPath变量保留为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 为只读:

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.
    } 
}


老实说,只是为了从字符串中将两个属性设置为特定类型,这似乎有点矫枉过正,你有很多选择——即使是隐式运算符也可以工作。

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

于 2012-07-12T13:32:51.207 回答
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"
        };

请注意,尽管在andResourceBase的签名中,在上述语句之后and都是对象。constructorMapSubscribeLocalPathFtpPath JavaScript

于 2012-07-12T14:13:44.747 回答
0

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

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

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

于 2012-07-12T13:45:30.840 回答
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); } }
}

应该可以改进。

于 2012-07-12T13:50:48.560 回答