[–] 1 point 10 months ago*

Conventionally (as much as the term could apply to this unconventional situation) the pattern I've seen has been to do a second GC.Collect(); right after the GC.WaitForPendingFinalizers();

Otherwise yes, without running it (I'm far enough away from a PC right now) I am highly confident that this is the answer (edit to add: minus the details of the descriptions of why this is the case, which conflict with my understanding of how this stuff works).

  • source
  • parent
  • context
  • [–] 1 point 10 months ago

    GC.Collect runs in a different thread and it is not blocked by waiting on terminal. And calling Collect is not just a piece of advice, it starts collection. And yes, usually one does not call Collect unless it knows why (there are cases it makes sense), but setting reference to null doesn't hurt (but doesn't help probably). As per destructors, those are for freeing unmanaged resources only.

  • source
  • parent
  • context
  • [–] 4 points 10 months ago (1 child)

    OTOH gc might decide not to collect it immediately since it's in a static method, try this instead

    DestructorDemo? obj1 = new DestructorDemo();
    //Making obj1 for Garbage Collection
    obj1 = null;
    for (int i = 0; i < 10; i++)
    {
        GC.Collect();
        GC.WaitForPendingFinalizers();
        obj1 = new DestructorDemo();
    }
    

    You'll see destructor in action.

  • source
  • [–] 2 points 10 months ago*

    Far more familiar with Java since I've mostly used that the last decade, but two questions come to mind...

    1. Does the console object buffer output? I know you're waiting for a key, but sometimes buffers do weird things like not flushing when you expect.
    2. Is GetType failing silently? I'm not familiar with the innards of C# any more, but in rare occasions in other languages, errors/exceptions get silently swallowed. Usually it's with funky code that amounts to a logical error and not a true magically swallowed exception, but...

    At least GC.Collect() is a blocking collect, but perhaps there could also be funky shenanigans happening? I'd also think it'd collect obj1, but it is still in the same scope it was created in. Maybe there could also be some funkyness there with objects only getting picked up if they're out of scope or similar?

    I'd imagine most of the above would be really easy to test if you can get a debugger to step through. If it doesn't hit at all, something odd is happening with flagging the objects for collection I'd think. If it hits but errors, easy peasy. If it hits and executes fine but doesn't show, maybe as others suggested, use a different function rather than something from Console or something?

  • source
  • [–] 2 points 10 months ago (2 children)

    Some ideas:

    It's been a long time since I used Java and C# but you were not supposed to write destructors, or "close objects" improperly by setting a null value, or calling collect explicitly.

    Also you're blocking the application with ReadKey to trick the terminal into keeping the window open. That could prevent the GC from working at this specific step because the main function is not over yet. Configure the IDE instead to keep the terminal open, and remove ReadKey.

    Calling collect is only a piece of advice to the GC, it could wait after you press the key but you wouldn't see it if the terminal closes too fast.

  • source
  • [–] 1 point 1 year ago (1 child)

    Not sure whether that is a c# specific challenge. Does c# not know the concept of laziness?

    Haskell famously is lazy, and so are Java message streams: memory objects simply don't get created unless you use them.

    I can understand that being a problem for trivial benchmarks, but I would assume a benchmarker to have that level of understanding, and otherwise catch up fairly quickly. I also had to learn about laziness when things just didn't want to do anything at all. The compiler just optimized it all away because it wasn't being used, as far as it could tell.

  • source
  • [–] 1 point 1 year ago

    No moreso than extension methods already did, I think. The only real concern is naming conflicts, which are generally an immediate compiler error that's easily resolved. Certainly a little more likely for something like an indexer, that doesn't HAVE a name, but what are the odds, really, of not only wanting to add an indexer to a type where it makes sense to have one and that type doesn't have one already, but also that there would be MULTIPLE different conflicting ways for that to make sense?

    Personally, I think this has been a long time coming.

  • source
  • parent
  • context
  • [–] 1 point 1 year ago (1 child)

    So if i want to handle different status code, is several exception filters or if statements cleaner. I just remember a colleague has done that in the past with if statements.

  • source
  • [–] 1 point 1 year ago* (last edited 1 year ago)

    This exactly what I described in 2021, so I'm pretty happy seeing other languages also going this route.

    Sadly, they also don't have a solution for the follow-up question that naturally falls out if this approach -- how to "unbox" generic types such that they can be used directly and don't need some additional type to hold it, i.e.

    union Option[T] of T, None // None defined elsewhere
    

    instead of

    union Option[T] of Some[T], None // Some & None defined elsewhere
    
  • source
  • [–] 2 points 1 year ago*

    Second one was, IIRC, one of the primary motivating use cases for IAsyncEnumerable<T>. It has to be IAsyncEnumerable<T> all the way up and down, but it's elegant enough. Quite often, depending on the API, it might naturally be implemented by a variant of "paging" behind the scenes. One advantage here is that since the updates throughout the final await foreach can happen entirely on the UI thread (assuming a compliant SynchronizationContext.Current, which is the case at least for WPF), you can see the results streaming in one-by-one as they arrive from the original repository if you want.

    Before watching the video, from the title, I correctly guessed what the first bug would be, but my guess for what bug #2 would be was one that's more related to the far more insidious one that he describes starting at 17:27. You don't have to get very fancy to see connections held open too long:

    I don't know how common it is more broadly, but I've seen plenty of code that overlooks using and just directly calls Dispose (or, more commonly, a Close method). While it's generally advised to favor using over directly calling Dispose, admittedly it's not always a huge deal: if you don't otherwise have a robust story for what happens when exceptions get thrown, then it doesn't ordinarily matter that you're not explicitly cleaning up those resources, since the program might just be ending anyway.

    With iterators, this can leave the connection open even if no exception is thrown. try/finally blocks (including using scopes, which I assume still get lowered to the same) get extra treatment to make sure that the finally part gets executed when the iterator is disposed, which happens at the end of any foreach loop over it (including the implicit loop behind calls like .First).

  • source
  • [–] 1 point 1 year ago

    F# is such a fantastic language.

    I'd not hesitate a second to use it (and Fable) for my own startup. I'm very comfortable with Ruby, Clojure, Haskell, Perl, PHP, Python, Java, Typescript, and C#. Yet I would pick F# over all of those in a heartbeat.

    F# has great type safety (but not forced into monad transformer chaos like Haskell). You need about 1/3rd less code than C# and Java, and the compiler will find more bugs than in those languages.

  • source
  • parent
  • context
  • view more: ‹ prev