2010-05-21 17 views
8

Docenisz następujące dwa cukry składniowe:Czy ta funkcja istnieje? Definiowanie własnych klamrowych w C#

lock(obj) 
{ 
//Code 
} 

same as: 

Monitor.Enter(obj) 
try 
{ 
//Code 
} 
finally 
{ 
Monitor.Exit(obj) 
} 

i

using(var adapt = new adapter()){ 
//Code2 
} 

same as: 

var adapt= new adapter() 
try{ 
//Code2 
} 
finally{ 
adapt.Dispose() 
} 

Wyraźnie pierwszy przykład w każdym przypadku jest bardziej czytelny. Czy istnieje sposób na zdefiniowanie tego rodzaju rzeczy samodzielnie, w języku C# lub w IDE? Powodem, dla którego pytam, jest to, że istnieje wiele podobnych zastosowań (długiego rodzaju), które skorzystałyby na tym, np. jeśli używasz programu ReaderWriterLockSlim, potrzebujesz czegoś bardzo podobnego.

EDIT 1:

Zostałem poproszony o podanie przykładu, więc dam mu szansę:

myclass 
{ 
ReaderWriterLockSlim rwl = new ReaderWriterLockSlim(); 

void MyConcurrentMethod() 
{ 
    rwl.EnterReadLock(); 
    try{ 
    //Code to do in the lock, often just one line, but now its turned into 8! 
    } 
    finally 
    { 
    rwl.ExitReadLock(); 
    } 
} 
} 

//I'd rather have: 
void MyConcurrentMethod() 
{ 
rwl.EnterReadLock() 
{ 
    //Code block. Or even simpler, no brackets like one-line ifs and usings 
} 
} 

Oczywiście trzeba by dać kilka myśli, jak korzystać z TryEnterReadLocks i tego rodzaju rzeczy z zwrotami. Ale jestem pewien, że możesz coś wymyślić.

+1

Można emulować obciążenie rzeczy za pomocą konstruktora jako "pre-block" i Dispose() jako „post-bloku "funkcja. – Dykam

+0

Czy możesz wyjaśnić, co przez to rozumiesz? – Carlos

+0

Tutaj możesz: http://stackoverflow.com/questions/2882983/does-this-feature-exist-defining-my-own-curly-brackets-in-c/2932023#2932023 – Dykam

Odpowiedz

19

Niezupełnie, ale można użyć delegata działania, aby uzyskać coś podobnego:

void MyBrace(Action doSomething) 
{  
    try 
    { 
     //wait for lock first 

     doSomething(); 
    } 
    finally 
    { 
     //special cleanup 
    } 
} 

i używać go tak:

MyBrace(() => 
{ 
    //your code goes here, but won't run until the lock is obtained 
}); // cleanup will always run, even if your code throws an exception 

pamiętać, że istnieją pewne ograniczenia w tym zakresie.Nie możesz na przykład mieć sensownej instrukcji return w nowych nawiasach klamrowych. Dzięki zamknięciu będziesz mógł nadal korzystać ze zmiennych lokalnych.

+2

+1: To jest całkiem ładne sprytny. –

+0

Podoba mi się, mógłbym spróbować napisać to do klasy rozszerzenia dla moich zastosowań rwlSlim. – Carlos

+0

Używam tego wzorca intensywnie i działa bardzo dobrze. Możesz robić rzeczy takie jak 'T WithTry (Func func, T valueIfFails)' –

1

Nie, nie ma sposobu na zdefiniowanie własnych słów kluczowych. Są one definiowane przez język i są wbudowane w kompilator w celu interpretacji do IL. Jeszcze nie osiągnęliśmy tego poziomu abstrakcji!

Wystarczy spojrzeć na to, jak bardzo podekscytowani są wszyscy, gdy wprowadzane są takie rzeczy, jak var i dynamic.

Mając to na uwadze, edytuj swój wpis, aby pokazać, jaka jest składnia dla przykładu ReaderWriterLockSlim. Byłoby ciekawie to zobaczyć.

5

Niestety nie. Aby to wspierać, kompilator C# musiałby być bardziej rozszerzalny dla użytkownika końcowego. Obejmowałoby to możliwość definiowania własnych słów kluczowych i obsługi makr itp.

Jednak można zbudować metody, które przynajmniej mają podobne odczucie: (przykro nam, że warto powtórzyć Hasło blokady kodem użytkownika)

public static class MyLocker 
{ 
public static void WithinLock(this object syncLock, Action action) 
{ 
    Monitor.Enter(syncLock) 
    try 
    { 
    action(); 
    } 
    finally 
    { 
    Monitor.Exit(syncLock) 
    } 
} 
} 

używając wtedy byłoby tak:

object lockObject = new object(); 
MyLocker.WithinLock(lockObject, DoWork); 

public void DoWork() 
{....} 

LUB

lockObject.WithinLock(DoWork); 

LUB

lockObject.WithinLock(()=> 
{ 
DoWork(); 
//DoOtherStuff 
}); 
1

Rozumiem, że w następnej wersji Visual Studio będzie duży nacisk na tego typu elastyczność.

Na skałach .net, Anders Hejlsberg powiedziałeś ostatnio, że jednym z głównych tematów następnego wydania Visual Studio (2012?) Będzie "kompilator jako usługa" i od tego czasu niektórzy inni zasugerowali, że otworzy to dużo drzwi do rozszerzenia języka i ściślejszej integracji z DSL (Języki specyficzne dla danej domeny).

W tej chwili możesz zrobić całkiem fajne rzeczy za pomocą DSL-ów, w tym tworzyć własne słowa kluczowe, chociaż moim zdaniem jest to nadal trochę zbyt niezręczne. Jeśli jesteś zainteresowany, sprawdź this article on designing DSLs in Boo.

To może trochę zdmuchnąć twój umysł.

3

Nie można zdefiniować konstruktów tak bezpośrednio, ale istnieją sposoby, aby tworzyć podobne wzorce zwięzłe:

Można zdefiniować klasy, które implementują IDisposable do hermetyzacji tego rodzaju semantyki użytkowania bloku. Na przykład, jeśli masz jakąś klasę, która zawiera enkapsulację odczytu Reader readrockLockSlim (nabywaj przy konstruowaniu, zwalniaj po usunięciu), możesz utworzyć własność na swojej klasie, która będzie konstruować instancję, co spowoduje składnię taką jak ta:

using (this.ReadLock) // This constructs a new ReadLockHelper class, which acquires read lock 
{ 
    //Do stuff here.... 
} 
//After the using, the lock has been released. 

Jest to prawdopodobnie nadużycie IDisposable, ale ten wzorzec został definitywnie użyty w kodzie produkcyjnym.

Można użyć programowania zorientowanego na aspekt (przy użyciu narzędzi takich jak PostSharp), aby owinąć treść metody w logikę wejścia/wyjścia wielokrotnego użytku. Jest to często używane do wstrzykiwania dzienników lub innych zagadnień przekrojowych, które chcesz zastosować do kodu bez zaśmiecania go.

Możesz pisać funkcje, które przyjmują delegatów jako parametry, które następnie zamykają delegowaną logikę w jakiejś podobnej strukturze. Na przykład, dla ReaderWriterLockSlim ponownie można stworzenie sposobu takiego:

private void InReadLock(Action action) 
{ 
    //Acquires the lock and executes action within the lock context 
} 

ta może być bardzo silne, ponieważ obsługa ekspresji lambda zamknięciami umożliwia dowolnie złożonej logiki bez ręcznego tworzenie funkcji opakowania i wymaga przepuszczenie parametry w polach.

+0

Myślałem, że IDisposable jest właśnie dla zasobów, które wymagają wyraźnego wydania. Dlaczego uważasz, że to nie jest prawda o zamku? –

+0

Osobiście uważam, że jest to uzasadnione użycie wzoru, ale jest ważna różnica. Większość klas IDisposable również implementuje finalizator dla swoich niezarządzanych zasobów, co oznacza, że ​​zasoby niezarządzane zostaną ostatecznie zwolnione. Jeśli nie uda ci się usunąć obiektu, który trzyma blokadę wątku do czasu wywołania Dispose, najprawdopodobniej stworzyłeś zakleszczenie. Jest to prawdopodobnie bardziej niebezpieczne. Istnieje również bardzo rzadki przypadek w .NET 3.5 i wcześniejszych, w których Dispose nie zostanie wywołany, jeśli wyjątek ThreadAbortEx zostanie zgłoszony we właściwym miejscu. –

+0

Wiele klas IDisposable nie posiada bezpośrednio zasobu niezarządzanego, więc nie ma finalizatora.Twój przykład jest rozsądniejszy, niż sugerujesz. –

1

Nie ma natywnej funkcji, która robi to, co chcesz. Można jednak wykorzystać oświadczenie using po prostu wykonując IDisposable.

public class Program 
{ 
    private static SharedLock m_Lock = new SharedLock(); 

    public static void SomeThreadMethod() 
    { 
    using (m_Lock.Acquire()) 
    { 
    } 
    } 
} 

public sealed class SharedLock 
{ 
    private Object m_LockObject = new Object(); 

    public SharedLock() 
    { 
    } 

    public IDisposable Acquire() 
    { 
    return new LockToken(this); 
    } 

    private sealed class LockToken : IDisposable 
    { 
    private readonly SharedLock m_Parent; 

    public LockToken(SharedLock parent) 
    { 
     m_Parent = parent; 
     Monitor.Enter(parent.m_LockObject); 
    } 

    public void Dispose() 
    { 
     Monitor.Exit(m_Parent.m_LockObject); 
    } 
    } 
} 
1

Osobiście abuse using bardzo za to, że mam klasy DisposeHelper na to:

class DisposeHelper : IDisposable { 
    private Action OnDispose { get; set; } 

    public DisposeHelper(Action onDispose) { 
     this.OnDispose = onDispose; 
    } 

    public void Dispose() { 
     if (this.OnDispose != null) this.OnDispose(); 
    } 
} 

To pozwala mi zwrócić IDisposable z dowolnej metody dość łatwo:

IDisposable LogAction(string message) { 
    Logger.Write("Beginning " + message); 
    return new DisposeHelper(() => Logger.Write("Ending " + message)); 
} 

using (LogAction("Long task")) { 
    Logger.Write("In long task"); 
} 

Można również po prostu zrobić to w linii:

rw1.EnterReadLock(); 
using (new DisposeHelper(() => rw1.ExitReadLock()) { 
    // do work 
    return value; 
} 

Albo, z dodatkiem onInit działaniu:

using (new DisposeHelper(() => rw1.EnterReadLock(),() => rw1.ExitReadLock())) { 
    // do work 
    return value; 
} 

ale to jest po prostu brzydki.

: Technicznie, przypuszczam, że to raczej nadużycia IDisposable niż to jest using. Ostatecznie, tak naprawdę chcę uzyskać do, chcę try/finally - co daje mi using. Okazuje się jednak, że muszę wprowadzić IDisposable, aby uzyskać try/finally. Tak czy inaczej, nie przeszkadza mi to. Ta semantyka jest dla mnie dość jasna - jeśli zaczniesz coś od, oczekuję, że skończysz także coś, co jest również. Np. Jeśli napiszesz do pliku dziennika komunikat "Beginning task", oczekuję również komunikatu dziennika "Ending task". Mam bardziej inkluzywną definicję "zwalniania zasobów" niż większość programistów.

0

Tak jak inni już powiedzieli, IDisposable może być nadużywane, aby utworzyć kod notion of a scope. Może nawet obsługiwać zagnieżdżanie.

1

Magiczna klasa:

// Extend IDisposable for use with using 
class AutoReaderWriterLockSlim : IDisposable { 
    ReaderWriterLockSlim locker; 
    bool disposed = false; // Do not unlock twice, prevent possible misuse 
    // private constructor, this ain't the syntax we want. 
    AutoReaderWriterLockSlim(ReaderWriterLockSlim locker) { 
     this.locker = locker; 
     locker.EnterReadLock(); 
    } 
    void IDisposable.Dispose() { // Unlock when done 
     if(disposed) return; 
     disposed = true; 
     locker.ExitReadLock(); 
    } 
} 
// The exposed API 
public static class ReaderWriterLockSlimExtensions { 
    public static IDisposable Auto(this ReaderWriterLockSlim locker) { 
     return new AutoReaderWriterLockSlim(locker); 
    } 
} 

Zastosowanie:

ReaderWriterLockSlim rwl = new ReaderWriterLockSlim(); 
using(rwl.Auto()) { 
    // do stuff, locked 
} 
Powiązane problemy