【问题标题】:Implicit operators and class hierarchies隐式运算符和类层次结构
【发布时间】:2012-07-13 09:45:29
【问题描述】:

我有以下类层次结构。

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 将是 @987654325 类型@。

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

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

【问题讨论】:

  • WebResourceBase 是否应该派生自 ResourceBase
  • 我会实现一个显式函数来映射 this 而不是使用隐式运算符。这将使在项目生命周期中维护和理解代码变得更加困难。
  • @Vince 抱歉,我现在看到了问题。我会修改我的答案。
  • 如果基类声明了一个接受字符串 (path) 并返回 ResourceBase 的抽象方法,那么 JavaScript 类可以通过返回 new JavaScript(path); 来实现该方法,并且以此类推。
  • 或者你应该简单地让你的JavaScript 类(和其他派生类)有一个带有两个字符串参数的构造函数。然后你可以在构造函数中做这些事情。您没有必须使用“对象初始化器”吗?

标签: c#


【解决方案1】:

这假设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 只读:

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


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

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

【讨论】:

    【解决方案2】:

    我也假设 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"
            };
    

    请注意,尽管constructorMapSubscribe的签名中有ResourceBase,但在上述声明之后LocalPathFtpPath都是JavaScript对象。

    【讨论】:

      【解决方案3】:

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

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

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

      【讨论】:

        【解决方案4】:

        这是我的想法:

        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); } }
        }
        

        可能会改进。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2020-09-14
          • 2012-08-25
          • 1970-01-01
          • 2015-09-24
          • 2021-09-11
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多