【问题标题】:Why does this code produce the same output for both method calls?为什么这段代码对两个方法调用产生相同的输出?
【发布时间】:2012-02-23 16:23:34
【问题描述】:

为什么这段代码对两个方法调用产生相同的输出?我会假设因为一种方法是普通的公共方法并且是从实例调用的,那么它会为静态方法调用生成一个不同的随机数,因为实例与为静态方法调用创建的实例是分开的?

class ClassWithStaticMembers
{

    public static int ReturnAnIntStatic()
    {
        Random random = new Random();
        return random.Next();
    }

    public int ReturnAnInt()
    {
        Random random = new Random();
        return random.Next();
    }
}

class Program
{

    static void Main(string[] args)
    {

        ClassWithStaticMembers classWithStaticMembers = new ClassWithStaticMembers();

        //We can do this because the method is declared static. 
        Console.WriteLine(ClassWithStaticMembers.ReturnAnIntStatic());

        //This can be used as we have not declared this method static
        Console.WriteLine(classWithStaticMembers.ReturnAnInt());

        Console.ReadKey();

}

}

输出如下:

12055544 12055544

有人能解释一下为什么使用类实例的方法调用会产生与静态方法的方法调用相同的结果吗?为方法调用生成的实例是否不同?

编辑:除此之外。是用于调用公共方法的 ClassWithStaticMembers 的实例,与静态调用分开。我的意思是,如果编译器识别出我稍后在文件中调用同一个类,它会再次使用相同的实例吗?

【问题讨论】:

    标签: c# static


    【解决方案1】:

    这是因为Random 默认情况下由当前报价播种,并且由于几乎同时调用这两个方法,它们将产生相同的数字。这在documentation中有解释:

    默认种子值来自系统时钟,具有有限的 解析度。结果,在 通过调用默认构造函数的紧密继承将具有 相同的默认种子值,因此,将产生相同的 随机数集。这个问题可以通过使用单个 随机对象生成所有随机数。你也可以解决 它通过修改系统时钟返回的种子值,然后 将这个新的种子值显式地提供给 Random(Int32) 构造函数。有关详细信息,请参阅 Random(Int32) 构造函数。

    在 2 个方法调用之间放置一个睡眠以观察差异:

    Console.WriteLine(ClassWithStaticMembers.ReturnAnIntStatic());
    Thread.Sleep(2000);
    Console.WriteLine(classWithStaticMembers.ReturnAnInt());
    

    或者为 Random 类使用不同的构造函数以不同的方式播种它们。或者简单地使用 Random 类的相同静态实例:

    class ClassWithStaticMembers
    {
        private static Random random = new Random();
    
        public static int ReturnAnIntStatic()
        {
            return random.Next();
        }
    
        public int ReturnAnInt()
        {
            return random.Next();
        }
    }
    
    class Program
    {
    
        static void Main()
        {
            var classWithStaticMembers = new ClassWithStaticMembers();
            Console.WriteLine(ClassWithStaticMembers.ReturnAnIntStatic());
            Console.WriteLine(classWithStaticMembers.ReturnAnInt());
            Console.ReadKey();
        }
    }
    

    这就是为什么当您需要真正的随机性而不是伪随机数时永远不应该使用 Random 类的原因。例如,在密码学中,您应该使用 RNGCryptoServiceProvider 而不是 Random 类。一旦您知道用于实例化 Random 类的初始种子值,您就可以预测该类将生成的所有数字。

    【讨论】:

    • 感谢您的明确答复。现在完全有道理。请参阅我对 OP 的进一步评论,就像我可以做的其他事情一样。
    • @CSharpened,为了调用实例方法,您需要一个类的实例。当您调用静态方法时,您根本没有使用任何实例。
    • 如果您根本没有任何实例,编译器如何知道代码在哪里?这是堆的特殊部分出现的地方吗?存储在那里的静态方法是否可以使用?
    • @CSharpened,因为静态类永远不会被实例化,所以没有对它的引用。它的方法只是当 CLR 加载程序集时加载到内存中的函数。它们不存储所谓的加载程序堆。我建议您阅读以下文章,以更好地了解 CLR 如何创建运行时对象的内部原理:msdn.microsoft.com/en-us/magazine/cc163791.aspx 但是,您最初的问题是关于 Random 类及其生成的原因,我们在这里有点偏离主题相同的值。与实例或静态类无关。
    【解决方案2】:

    猜测:可能是因为使用了随机数生成器。通常,如果你初始化一个生成器(用任何语言),它会使用当前时间作为种子。在您的情况下,这大约发生在同一时间,因此生成器将返回相同的数字。

    换句话说:如果你改为使用 Console.WriteLine() 会发生什么? (或者更确切地说返回一个 const int)

    编辑:同时发生,而不是大约,滴答数是相同的。

    【讨论】:

    • 我明白了,这不是实例相同的情况,而是导致这种情况的随机生成器使用的时间巧合?我不确定它是否是编译器重用的静态实例来运行对公共方法的调用。
    • 文档甚至警告过这种情况:msdn.microsoft.com/en-us/library/h343ddh9.aspx 必须为随机数生成器播种,而且看起来 C# 使用分辨率相当低的时间源作为(默认)种子。可能只是以秒为单位使用时间!
    • 没错,在一种方法中返回 5,在另一种方法中返回 7,或者按照 Darin 的建议进行 sleep。
    • @Kitsune:根据记忆,我认为分辨率约为 15ms。比一秒小得多,但在现代机器上,这 15 毫秒的时间显然会发生很多事情。
    • @LukeH 啊,好一点,但从计算机的角度来看,15ms 仍然相当大!在 Windows 上,15 毫秒大约相当于 三个 个时间片!
    【解决方案3】:

    这与静态成员和实例成员的区别无关。

    您在每个方法中实例化了一个 Random 实例,因为这些方法被快速连续调用,所以两个 Random 实例将使用相同的种子,因此产生相同的输出。

    您可以使用一个更简单的示例来证明静态方法和实例方法是不同的:

    class ClassWithStaticMembers
    {
        public static int ReturnAnIntStatic()
        {
            return 123;
        }
    
        public int ReturnAnInt()
        {
            return 42;
        }
    }
    

    【讨论】:

      【解决方案4】:

      因为在这两种情况下,您都在使用Random 类的实例;由于它们都将在非常相似的时间实例化,因此会产生相同的输出。

      如果您使用原始代码,但在 2 个方法调用之间引入了等待,它会产生不同的数字,根据这个实时示例:http://rextester.com/UNH72532

      使用Random 类的正确方法是拥有它的 1 个静态实例

      class ClassWithStaticMembers
      {
          private static Random rnd = new Random();
          public static int ReturnAnIntStatic()
          {
              return rnd.Next();
          }
          public int ReturnAnInt()
          {
              return rnd.Next();
          }
      }
      

      这两种方法现在都会产生一个随机数,请根据您的代码检查这个实时示例:http://rextester.com/NBND87340

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2022-12-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-02-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多