Replies: 21 comments 2 replies
-
Depending how popular this becomes, it makes the job much harder when For example, I'll often return a struct enumerable and enumerator and awaitable and awaiter. It's also extremely common for me to return an If we had BCL methods to handle this for us it'd be terrific. |
Beta Was this translation helpful? Give feedback.
-
I would be happy about allowing |
Beta Was this translation helpful? Give feedback.
-
@HaloFour Is there some additional benefit besides avoiding a wrapper class for the non-standard disposable? I mean, you could use
|
Beta Was this translation helpful? Give feedback.
-
On the surface, I'm not a big fan of this. In C# 1.0, the using statement allowed any class which simply implemented a Keep in mind that the following code public void DoWork()
{
using (MyClass myClass = new MyClass())
{
myClass.SomeMethod();
}
} is roughly translated by the compiler to public void DoWork()
{
MyClass myClass = new MyClass();
try
{
myClass.SomeMethod();
}
finally
{
if (myClass != null)
{
IDisposable disposable = (IDisposable)myClass;
disposable.Dispose();
}
}
} There are already enough abuses of the disposable pattern and the I think what's really needed is a new pattern to support scoped units of work that has nothing to do with resource allocation and disposability. |
Beta Was this translation helpful? Give feedback.
-
@scottdorman I don't think that using
That would be #85. It adds capabilities that |
Beta Was this translation helpful? Give feedback.
-
Structs shouldn't implement It is an abuse of the pattern because it has nothing to do with resource allocation. Disposability is supposed to be about deallocating resources, both managed and unmanaged. It has nothing to do with a unit of work ending. Consider a simple "cursor changer" class: public class WaitCursor : IDisposable
{
public WaitCursor()
{
IsWaitCursor = true;
}
public void Dispose()
{
IsWaitCursor = false;
}
public bool IsWaitCursor
{
get
{
return Application.UseWaitCursor;
}
set
{
if (Application.UseWaitCursor != value)
{
Application.UseWaitCursor = value;
Cursor.Current = value ? Cursors.WaitCursor : Cursors.Default;
}
}
}
} This isn't allocating resources at all, it's simply changing the system cursor. It abuses the Yes, #85 proposes a mechanism to do scoped units of work, but presents it in a very narrowly defined usage pattern, namely for instrumentation. It really shouldn't be so narrow/specific but should be a proposal for a scoped unit of work. |
Beta Was this translation helpful? Give feedback.
-
I have to agree with @jnm2, and I think that "abuse" is an abused term here. The |
Beta Was this translation helpful? Give feedback.
-
Even structs wrapping disposable classes? I really disagree. What about things like I have no problem stretching the meaning of resource to include usage of a different cursor. I don't see any reason that // foreach over every node in a tree
var stack = new StackEnumerator(root);
foreach (var node in stack)
{
// ...
stack.Recurse(node.Children);
} I maintain that it is not abuse; it's using a language feature to its full potential with no pitfalls. |
Beta Was this translation helpful? Give feedback.
-
As to the convension, I'd like a builder pattern rather than an extension method. https://gist.github.com/ufcpp/1c7977f4f5f7856787f3f2b6a8b13c8e In this, I want to call using (var x = new Sample(1, 2).Init())
{
for (int i = 0; i < 10; i++)
{
items.Add(x.Share());
}
} // Call x.Release() instead of x.Dispose() |
Beta Was this translation helpful? Give feedback.
-
That's actually my point. It's a nice syntax for a unit-of-work pattern and it's the only one available to us in the language today, so we repurpose the code to do something outside of it's intent. I'll grant you that I'm being ideological here, but I'm doing it because I think it's an important distinction to make between resource allocation/deallocation and transactional/unit of work scopes. It's sort of like saying that "All types which implement a finalizer should be disposable, but not all disposable types should implement a finalizer." A type which implements Given that statement, I could see @jnm2 The problem is that there is a well defined pattern for how public void Dispose()
{
Dispose(true);
GC.SupressFinalize(this);
} That absolutely has everything to do with deallocating resources. As for |
Beta Was this translation helpful? Give feedback.
-
Cleaning up resources such as a scope registration to restore the cursor if you forgot to call Dispose, yes. All |
Beta Was this translation helpful? Give feedback.
-
Isn't this something which is easily solved with Rx's |
Beta Was this translation helpful? Give feedback.
-
Yes, Rx Disposable helpers do provide a simple way to work around this limitation. Although using it does effectively require at least one allocation. And Rx does at least demonstrate that not all of MS is so zealous about disposables strictly being about unmanaged resources. |
Beta Was this translation helpful? Give feedback.
-
Given that logic, then
Are you referring to following the pattern here? The pattern isn't a stopgap. For that matter, neither is implementing a finalizer, if and when it's necessary. (Finalizers, for the most part, aren't actually necessary anymore, but there are still times when they are.) @HaloFour, @vladd Yes, the Rx Disposable helpers provide a way to help ensure the pattern is followed in much the same way (conceptually at least) as
That's true but, to some extent I think, it's because there is no other way of achieving the design/syntax goals. |
Beta Was this translation helpful? Give feedback.
-
To my understanding, unit of work and instrumentation are pretty much the same concept. If not, what is missing on #85 for supporting "scoped unit of work"? |
Beta Was this translation helpful? Give feedback.
-
@msfcolombo Yes, they are pretty much the same concept, but it's presented specifically in the context of instrumentation, to the point of having the |
Beta Was this translation helpful? Give feedback.
-
You can follow the logic to any conclusions you like, but the logic still holds. The dispose finalizer pattern calls Dispose(false), and Dispose(false) has never been permitted to do anything but release unmanaged resources. If your class does not have unmanaged resources, i.e. is not responsible to close an unmanaged handle, it is a no-op. Furthermore, if you use SafeHandles, any other finalizers are redundant because the GC will run them both. Of course base classes must implement the no-op pattern in case a derived class is the sole owner of an unmanaged handle and needs the protected Yes, the dispose finalizer pattern is a stopgap in almost every case. There are extremely few scenarios where it should ever run. In almost every case, you should have disposed deterministically in scope and not allowed the GC to get there first. That's why I find putting |
Beta Was this translation helpful? Give feedback.
-
Let me start with "Unit of Work" and "Resources" are pretty abstract terms, in fact I'd say that they are just fancy words for "Process" and "Things" respectively. The
Now, the instance of the locker that is passed to Variations of this pattern is implemented in the framework but as far as I understand by your definition this would be an "abuse" right? because at the end I'm just creating a scope that prevents multiple threads from reading or writing. :) In my opinion it really depends on the definition of resources and how much you zoom in to say it's invalid or zoom out to say otherwise. |
Beta Was this translation helpful? Give feedback.
-
👍 |
Beta Was this translation helpful? Give feedback.
-
If we just make // In corefx
public delegate void Disposal();
public static class DisposeExt
{
public void Dispose(this Disposal disposal) => disposal?.Invoke();
}
// in our code
using () => DoSomething(); // just convert to Disposal And if we include #1473 This would also be an #85 // In corefx
public delegate void Disposal();
public static class DisposeExt
{
public void Dispose(this Disposal disposal) => disposal?.Invoke();
}
// in our code
public (object obj,Disposal instrument) GetObjectWithInstrument()
{
return (a,() => DisposeLogic());
}
using(var (obj,_) = GetObjectWithInstrument()) /// would be great if we could convert (T,Disposal) to just (T)
{
DoSomethigWithT
} // _.Dispose(); obj.Dispose(); |
Beta Was this translation helpful? Give feedback.
-
I believe this was implemented in C# 8: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-8.0/using#pattern-based-using |
Beta Was this translation helpful? Give feedback.
-
Copied from dotnet/roslyn#11420
Currently the
using
statement is tied specifically to implementation of theIDisposable
interface. This makes it impossible to use resource-like objects from other assemblies withinusing
statements.I propose that the compiler allow resolving an accessible
void Dispose()
method in the case that the type does not implementIDisposable
. That would allow for a developer to extend an existing class through the use of an extension method which would contain the logic for "disposing" of that resource:This brings
using
in line with the conventions also established byforeach
, LINQ andawait
where the compiler can resort to resolving instance members of a specific name/shape rather than require implementation of an interface or base class.Beta Was this translation helpful? Give feedback.
All reactions