如何存储静态类的引用?

15

所以类似这样:

public static class StaticClass {}

public class InstanceClass
{
    static StaticClass StaticProperty {get;set;}

    public InstanceClass()
    {
        InstanceClass.StaticProperty = StaticClass;
    }
}

我以为可以这样做,但编译器返回以下错误:

静态类型不能用作参数

静态类型不能用作返回类型

编辑:我知道这行不通,但为什么呢?我想像 StaticClass 被存储在内存中的某个地方,所以其他变量可以在同一块内存中引用它,对吧?

编辑2:其中一个使用案例可能是这样的:

说你收集了5个不带源代码的不同静态类,并且它们都执行通用任务,因此你希望通过单个静态类方便地访问它们。你可以这样做:

public static class GenericStuff
{
    public LinearAlgebra LinearAlgebra {get;set;}
    public StringUtilities String {get;set;}
    public GeometryOps Geometry {get;set;}
}

然后像这样使用:

GenericStuff.LinearAlgebra.GetAngleBetweenVectors(v0, v1);

你可以考虑一些其他的使用情况。


你能解释一下如果你能实现你所写的,你打算如何使用这个属性吗? - R. Martinho Fernandes
1
我认为你要找的是元类。不幸的是,这个概念在C#中不存在。 - Jordão
我了解你的痛苦。我正在尝试做同样的事情,因为它可以让我使用静态类在服务器端定义常量,并在Razor代码中访问这些常量,而不必在JavaScript中重复定义它们。 - ProfK
10个回答

21

更新:我将使用我的心灵力量来尝试猜测您正在尝试做什么。

我猜您想要从另一个类中访问一些静态类中的方法。是这样吗?

换句话说,就像这样:

static class HelperMethods
{
    public static void SomeHelperMethod();
}

你想要做的是像这样吗?

class SomeOtherClass
{
    public void MethodThatUsesHelperMethod()
    {
        // You want to be able to have "Helper" mean "HelperMethods"?
        Helper.SomeHelperMethod();
    }
}

如果我理解您的意思是对的,那么我只能想到一种方法来部分地实现您想要的目标。这就是添加一个using声明来有效地给静态类型取别名:

// At top of file
using Helper = HelperMethods;
请注意,如果你这样做,你会创建一个文件范围的别名。没有办法只在类级别上为类创建别名。
StaticClass 是类的名称。你的 StaticProperty 属性期望类的一个实例,但由于该类是静态的,所以永远不可能存在这样的实例。 事实上,我很惊讶你甚至可以将属性标记为静态类,因为它代表了一种完全不可能的情况。(哦,等等,你不能这样做;这就是你所说的。)
你说你想存储对“静态类的引用”; 我必须假设你指的是要引用表示该类的 Type 对象,如果是这样,你应该这样做:
public Type StaticProperty { get; set; }

// ...

StaticProperty = typeof(StaticClass);

1
@Joan:你知道吗,我的心灵感应是正确的(请看我的更新)! ;) - Dan Tao
谢谢,你说得对,那正是我想做的,但不是在文件级别而是类级别上,这正是你所提到的。但为什么不允许呢?毕竟using也在做这件事,并且其他形式也可以被允许。 - Joan Venge
1
谢谢,我之所以问这个问题是因为我知道其他语言具有类似的静态类语义,但允许在任何级别和任何形式上进行“别名”操作。它们也不实例化静态类,所以我认为C#必须故意避免这种情况,可能是因为被认为不太实用或成本超过了收益等原因。 - Joan Venge
@Martinho:你可能是对的。我不是语言专家。这些大多是不太流行的特定领域语言。但我认为我在Ruby中也看到过它。 - Joan Venge
@Joan:在Ruby中,每个类都是Class类型的对象。这就是为什么你可以在Ruby中将一个类分配给一个变量的原因。与C#的主要区别是:1)在Ruby中,你只需要使用类名就可以获取表示类的对象,而在C#中,你需要使用typeof;2)在Ruby中,作为一种动态语言,动态调用方法很容易。在C#中,你需要使用反射或v4中的dynamic - R. Martinho Fernandes
显示剩余4条评论

7

静态类既是抽象的,又是密封的(查看生成的 IL 可以了解)。因此,您无法创建它的实例,也无法对其进行子类化以拥有子类的实例。这种组合使得您永远无法引用静态类的实例。

现在,要让对静态类的引用按照您想要的方式工作,您需要在 C# 中使用 metaclasses 或一些不同类型的别名。

要实现今天您想要的效果,您必须手动将包装类中的所有方法委托给所需的静态类,或放弃静态类型并使用 dynamic

public class StaticWrapper : DynamicObject {
  Type _type;
  public StaticWrapper(Type type) {
    _type = type;
  }
  public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) {
    var method = _type.GetMethod(binder.Name, BindingFlags.Static | BindingFlags.Public, null, args.Select(a => a.GetType()).ToArray(), null);
    if (method == null) return base.TryInvokeMember(binder, args, out result);
    result = method.Invoke(null, args);
    return true;
  }
  // also do properties ...
}

使用方法:

public static class GenericStuff {
  public readonly dynamic LinearAlgebra = new StaticWrapper(typeof(LinearAlgebra));
  public readonly dynamic String = new StaticWrapper(typeof(StringUtilities));
  public readonly dynamic Geometry = new StaticWrapper(typeof(GeometryOps));
}

2
我喜欢你那相当有创意的回答! - ProfK

6

C#规范第§8.7.12节内容如下:

如果一个类不打算被实例化,且仅包含静态成员,则应将其声明为静态类。这种类的示例包括System.ConsoleSystem.Environment。静态类是隐式密封的,没有实例构造函数。只能使用typeof运算符和访问类的元素来使用静态类。特别地,静态类不能用作变量的类型或用作类型参数。

由于静态类没有构造函数,因此无法实例化它。由于它是密封的,因此无法对其进行子类化并创建子类的实例。即使您可以对其进行子类化,也无法调用基础构造函数,因此仍然无法实例化它。

由于无法创建静态类类型的对象,因此将其用作返回类型是没有意义的。

由于StaticClass是一个类型名称,而不是一个表达式,因此您无法将其作为参数传递(在您的情况下,传递给属性设置器)。但是,您可以使用表达式typeof(StaticClass)获得表示它的Type类的实例。


3

您不能存储静态类的引用。您只能存储实例的引用,而静态类没有实例(尽管静态类可能有实例成员)。


1

我认为这就是你想要表达的意思:

好的,如果你不想实例化它,那么你的C#代码需要进行一些微调。假设你的静态类实现了一个属性和/或方法。

public static class StaticClass
{
    public static string StaticProperty {get; private set; }

    public static void StaticMethod() { //DoSomething }
}

您可以在InstanceClass中转发属性和函数定义,注意您必须为要调用的方法/属性的静态类名添加前缀。

public class InstanceClass
{
   private string StaticProperty
   {
         get { return StaticClass.StaticProperty; }
   }

   private StaticMethod()
   {
        StaticClass.StaticMethod();
   }

   public InstanceClass()
   { }
}

我认为像这样使用InstanceClass作为包装器有点复杂和不必要。我发现尽量减少代码库中静态类和方法的需求是值得尝试的。在测试和调试时,它们会引起各种头疼。


不是要创建 StaticClass 的实例,而是要引用它(不是 C# 术语)。 - Joan Venge
谢谢,是的,如果我这样做,那么我必须为我需要公开的每个方法提供一个单独的成员。 - Joan Venge
没错。我认为偶尔使用公用类作为静态类是可以的,但如果你发现自己需要向每个函数都传递相同的参数...那就不应该使用静态类了。 - Ritch Melton
我同意Ritch的观点,或者如果你有一些全局单例样式类型,我也会使用静态方式。比如像FileManager这样的类。 - Joan Venge
我也不相信Singleton模式。而是使用简单工厂和实例化类的方式。如果可以的话,我会禁止使用static关键字,因为它只会带来麻烦。 - Ritch Melton

1
我认为使用命名空间功能将是实现您所需的最佳方法。

LinearAlgebra.cs

namespace GenericStuff
{
    public static class LinearAlgebra
    {
        public static TypeOfResult Function() { ... }
    }
}

Strings.cs

namespace GenericStuff
{
    public static class Strings
    {
        public static TypeOfResult Function() { ... }
    }
}

Geometry.cs

namespace GenericStuff
{
    public static class Geometry
    {
        public static TypeOfResult Function() { ... }
    }
}

所有这些都可以从GenericStuff开始调用。

var s = GenericStuff.Strings.Random(7);
var identity = GenericStuff.LinearAlgebra.Identity(3);
var square = GenericStuff.Geometry.Square(5);
var area = square.Area();

1

是的,但我并不想创建一个实例,只是想存储它的引用。 - Joan Venge
@Joan - 因为它没有任何实例,所以也不能有对它的引用。 - 48klocs
1
通过引用,我并不一定指的是.NET或C#的引用,而是一种类似于别名的东西,一个指向我的静态类的别名。 - Joan Venge
@48klocs 当程序加载时,不会有一个实例被创建吗? - Jay

0

你不能这样做。一个类不是它自己的实例。"Dog"不是一只狗。你可以将typeof(StaticClass)赋值给类型为Type的字段:

static StaticClass StaticProperty {get; set}
InstanceClass.StaticProperty = typeof(StaticClass);

这让你可以在类型上使用反射。


1
一个类不是它自己的实例,但每个静态类都有一个实例。一切皆为对象。 - ProfK

0

取决于你最终想要实现什么。

如果你只想在编译时而不是运行时更改一个类(即将使用相同版本的静态文件),那么你可以轻松地配置你的应用程序或者制作几个具有不同实现的相同文件的版本。

这种方法对于例如翻译等在静态文件中的情况非常有用。


0
我认为OP想要的是一种通过您所知道的“代理”轻松访问其他类的方法。
因此,假设您有一个名为MapHelpers的类:
public class MapHelper
{
            public static string CalculateNearLocation (Vector3 position){...}
}

你可能有很多其他的“助手”,你并不真正记得它们,只是想让它们容易地被访问。因此,你想要将它们“存储”在你的“助手”类中,这样你就可以记住你把它们放在哪里。

你可以选择这样做:

public class Helpers
{
  public class MapHelpers : MapHelper{}
}

并能够通过以下方式访问您的MapHelper:

Helpers.MapHelpers.CalculateNearLocation(pos)

或者这样做:

public partial class Helpers
{
}

public partial class Helpers
{
     public class MapHelper
     {
            public static string CalculateNearLocation (Vector3 position){...}
     }
}

并能够通过以下方式访问:

Helpers.MapHelper.CalculateNearLocation(pos)

然而,第一种方法会在IDE(如果您设置了)上警告您通过派生类型访问静态方法。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接