Czy lepiej napisać bibliotekę .NET z uwzględnieniem ograniczeń COM, czy oddzielić bibliotekę .NET od Interop?

9

Natrafiłem na ten interesujący artykuł: How I Came to Love COM Interoperability on CodeProject, co skłoniło mnie do myślenia ...

Autor twierdzi, że nie chce żadnych COM w swojej bibliotece .NET, ponieważ odbiera to piękno ich biblioteki .NET. Zamiast tego wolą napisać osobną bibliotekę Interop, która udostępnia ich bibliotekę .NET COM. Ta biblioteka Interop poradziłaby sobie z faktem, że COM nie obsługuje konstruktorów z parametrami, metodami przeładowanymi, rodzajowymi, dziedziczeniem, metodami statycznymi itp.

I choć myślę, że jest to bardzo nowatorskie, czy to nie tylko komponuje projekt?

  • Teraz musisz przetestować jednostkę bibliotekę .NET ORAZ bibliotekę Interop.
  • Teraz musisz poświęcić czas na zastanawianie się, jak obejść swoją piękną bibliotekę .NET i udostępnić ją COM.
  • Musisz podwoić lub potroić liczbę swoich klas.

Z pewnością rozumiem, czy potrzebujesz swojej biblioteki do obsługi zarówno COM, jak i non-COM. Jeśli jednak zamierzasz używać modelu COM, czy tego rodzaju konstrukcja przynosi korzyści, których nie widzę? Czy zyskujesz tylko zalety języka C #?

A może ułatwia wersjonowanie biblioteki, zapewniając opakowanie? Czy sprawia, że ​​testy jednostkowe przebiegają szybciej, ponieważ nie wymagają użycia COM?

robodude666
źródło
2
Masz na myśli, oprócz tego, że możesz użyć przydatnych narzędzi, aby ulepszyć / wyczyścić prawdziwy kod? I zapewniając jasną ścieżkę migracji dla tych (prawdopodobnie starszych) materiałów COM?
Telastyn
3
To jest jak wzór fasady napisany dużą czcionką w całej przestrzeni nazw. Myślę, że rozsądnie jest tak to zorganizować, jeśli chcesz korzystać z funkcji .NET, ale naprawdę potrzebujesz OLE Automation. Twoje opakowania COM będą w zasadzie konwertować nowoczesne (niezmienne? Ogólne?) Idiomy na starsze obiekty COM z konstruktorami bez parametrów, silnie zmiennymi obiektami i SAFEARRAY dla twoich list ogólnych. Jeśli wspierasz inne komponenty .NET lub jesteś całkowicie zakochany w nowych stylach, miałoby to sens. Jeśli wspierasz TYLKO COM, to dlaczego nie napisać całego w VB6 (32 bity) i zachować tylko jeden idiom?
Mike
3
@MasonWheeler: Jest przestarzałe w taki sam sposób, że całe oprogramowanie starsze niż 6 miesięcy jest „starsze”, mam rację?
whatsisname
2
@MasonWheeler COM nie może być amortyzowany bez względu na to, ile osób (w tym niektóre grupy w Microsoft) by tego życzyło, ponieważ nadal jest najlepszym wyborem do kontrolowania procesów pozaprocesowych.
Mike
2
@Mike, właściwie patrzę na przeniesienie projektu VB6 do C # .NET. Interfejsy API, z których korzystamy, są bardzo proste, jednak kodu za kulisami nie da się utrzymać w obecnej formie. Mam nadzieję zaimplementować większość bibliotek w C # .NET i zapewnić Interop Facade, który zapewnia proste interfejsy API, które współdziałają z różnymi klasami.
robodude666

Odpowiedzi:

7

Jeśli jednak zamierzasz używać modelu COM, czy tego rodzaju konstrukcja przynosi korzyści, których nie widzę?

Ten fragment pytania jest tutaj najważniejszą częścią decyzji projektowej, a odpowiedź jest (jak zawsze) zależna .

Jeśli zamierzasz korzystać z biblioteki tylko przez COM Interop, powinieneś zaprojektować cały system, mając to na uwadze. Nie ma powodu, aby trzykrotnie zwiększyć liczbę linii. Im więcej LoC w systemie, tym więcej będzie wad. Dlatego należy zaprojektować system tak, aby korzystał tylko z funkcji zgodnych z COM.

Jednak , nie mogę myśleć o dobry powód, aby ograniczyć korzystanie z biblioteki .NET do COM. Dlaczego nie chcesz mieć możliwości używania zestawu w innym kodzie .Net? Ograniczanie się w ten sposób nie ma sensu.

Mam z tym pewne doświadczenie, więc podzielę się tym, jak sobie z tym poradziłem. Z pewnością nie jest idealny. Dokonałem pewnych kompromisów. Używam tylko elewacji dla klas COM (naprawdę interfejsy), które są niezgodne z nowszymi idiomami .Net, w przeciwnym razie bezpośrednio udostępniam interfejs .Net COM. Zasadniczo oznacza to, że używam elewacji dla każdego interfejsu, który używa ogólnych. Generics to jedyna rzecz, na którą się natknąłem, która jest po prostu niekompatybilna. Niestety oznacza to fasadę dla wszystkiego, co zwraca IEnumerable<T>, co jest dość powszechne.

Jednak nie jest to wcale tak złe, jak się wydaje, ponieważ klasa elewacji dziedziczy po klasie .Net i implementuje określony interfejs Interop. Coś takiego.

namespace Company.Product.Feature
{
    public class MyClass : INetInterface 
    {
        // lots of code
    }
}

namespace Company.Product.Interop
{
    public class MyClass : Feature.MyClass, IComInterface
    {
        // over ride only the  incompatible properties/methods
    }
}

Dzięki temu zduplikowany kod jest absolutnie minimalny i chroni interfejs COM przed „niezamierzonymi” zmianami. Gdy zmienia się podstawowa klasa / interfejs, klasa adaptera musi opanować więcej metod (lub przerwać interfejs i podważyć główny numer wersji). Z drugiej strony nie cały kod Interopa jest przechowywany w Interopprzestrzeni nazw, co może prowadzić do mylącej organizacji plików. Tak jak powiedziałem, to kompromis.

Aby uzyskać pełny przykład tego, możesz przeglądać foldery SourceControl i Interop mojego repozytorium. ISourceControlProvideri GitProviderbędą szczególnie interesujące.

Gumowa kaczuszka
źródło
Dzięki za odpowiedź @RubberDuck! Kilka dni temu natknąłem się na projekt RubberDuck; czytanie kodu było bardzo interesujące. Czy jesteś w stanie przejść IEnumerabledo VBA? Ponadto, Rubberduck.Interop.GitProviderdlaczego zdefiniowałeś sparametryzowane konstruktory, jeśli COM ich nie obsługuje?
robodude666,
Tak, możesz przekazać IEnumerableCOM, ale musi to być starsza, ogólna wersja. Jeśli chodzi o konstruktorów, spójrz na SourceControlClassFactory. Zasadniczo fałszujesz go, inicjując instancję klasy, która nie potrzebuje parametrów dla jej ctor i wykorzystując ją do uzyskania dostępu do nowych instancji klas interfejsu API.
RubberDuck
Zgaduję, że cokolwiek zdefiniowane w twoich ComVisible(true)klasach / interfejsach, które nie obsługuje COM, jest po prostu „ignorowane”? Zgaduję również, jeśli masz klasę o tej samej nazwie zdefiniowaną w wielu przestrzeniach nazw, musisz podać unikat, ProgIdjeśli chcesz ujawnić więcej niż jedną z nich?
robodude666,
Tak. Zgadza się, a staticmetody są ignorowane. Próbuję sprawdzić, czy jest dostępny odpowiednik VB6 VB_PredeclaredId. Myślę, że może być możliwe utworzenie domyślnej instancji.
RubberDuck
7

zabiera to piękno ich biblioteki .NET

Ten autor potrzebuje buziaka w twarz. Celem każdego profesjonalnego projektu jest stworzenie działającego oprogramowania, z którego ludzie chcą korzystać. Wszelkie błędne ideały dotyczące piękna pojawiają się długo potem. Jeśli to ich jedyne uzasadnienie, autor stracił całą wiarygodność.

Umiejętność oznaczania swoich klas jako widocznych i możliwość komunikowania się z kodem .NET przez COM jest niezwykle przydatna. Odrzucenie tej wielkiej korzyści dla produktywności dla piękna jest szalone.

Jednak sprawienie, by rzeczy działały z COM, wymaga pewnych kompromisów, wymaganie konstruktora bez argumentu jest jednym z nich, a niektóre z innych rzeczy, o których wspomniałeś. Jeśli są to funkcje, których i tak nie używasz, lub nie zaszkodzi ci ich nieużywanie, wtedy musisz zaoszczędzić dużo pracy, używając tej samej klasy dla COM i non com.

Z drugiej strony, jeśli Twoi klienci COM oczekują zupełnie innego interfejsu, mają zależności lub inne ograniczenia, które są nieprzyjemne w przypadku klas .NET, lub oczekujesz, że znacząco zmienisz swoje klasy .NET i będziesz musiał zachować COM - kompatybilność, a następnie należy stworzyć klasę Interop, aby wypełnić tę lukę.

Ale nie myśl o „pięknie”. Możliwość ujawnienia COM przez .NET ma na celu zaoszczędzić ci pracy. Nie twórz więcej pracy dla siebie.

Jaka jest nazwa?
źródło
+1. Podczas gdy uwielbiam piękno czarnych przycisków na czarnej skrzynce, praktyczne są znacznie ważniejsze.
gbjbaanb
W rzeczywistości osoba, która wprowadziła do tej dyskusji pojęcie „piękno”, może potrzebować budzenia, a to nie był autor tego drugiego artykułu.
Doc Brown
2
Na marginesie, jeśli twój obecny konstruktor wymaga argumentów, najlepiej jest zapewnić fabrykę klas dla komponentów COM, zamiast przeprojektować istniejący interfejs / interfejs API.
RubberDuck
1
Czy można wystawić swoją fabrykę jako klasę „GlobalMultiUse”? Więc możesz to zrobić Set oObject = MyCOMInterop.NewMyClass()bez konieczności tworzenia nowego obiektu Factory?
robodude666,
To cholernie dobre pytanie @ robodude666. Teraz, kiedy o tym wspominasz, mam taką nadzieję .
RubberDuck
3

Cóż, szczerze mówiąc, autor tego artykułu nie użył słowa „piękno” nigdzie w swoim artykule, to byłeś ty, co może wywrzeć na nim stronnicze wrażenie w przypadku tych, którzy nie poświęcili czasu na przeczytanie artykułu sami.

O ile rozumiem ten artykuł, biblioteka .NET już istniała i została napisana z myślą o COM - prawdopodobnie dlatego, że w momencie tworzenia biblioteki nie było wymogu obsługi COM.

Później wprowadzono dodatkowy wymóg obsługi COM. W takiej sytuacji masz dwie opcje:

  • przeprojektuj oryginalną bibliotekę lib, aby była kompatybilna z interfejsem COM (z ryzykiem uszkodzenia)

  • pozwól oryginalnej bibliotece roboczej przeważnie taką jaka jest, a zamiast tego utwórz oddzielny zespół z funkcją „otoki” (lub „adaptera”)

Tworzenie opakowań lub adapterów jest dobrze znanym wzorcem projektowym (lub w tym przypadku bardziej wzorem architektonicznym), a zalety i wady są również dobrze znane. Jednym z argumentów przemawiających za tym jest lepsze „rozdzielenie obaw”, a to nie ma nic wspólnego z pięknem.

Do twoich obaw

Teraz musisz przetestować jednostkę bibliotekę .NET ORAZ bibliotekę Interop.

Po wprowadzeniu interfejsu COM do istniejącej biblioteki .NET poprzez przeprojektowanie, należy przetestować ten interfejs. Wprowadzenie ręcznie napisanego adaptera może oczywiście wymagać dodatkowych testów działania interfejsu, ale nie ma potrzeby kopiowania wszystkich testów jednostkowych podstawowej funkcjonalności biznesowej biblioteki lib. Nie zapominaj, że to tylko kolejny interfejs do lib.

Teraz musisz poświęcić czas na zastanawianie się, jak obejść swoją piękną bibliotekę .NET i udostępnić ją COM.

Kiedy trzeba przeprojektować oryginalną bibliotekę, trzeba to również wymyślić i myślę, że będzie to o wiele więcej pracy.

Musisz podwoić lub potroić liczbę swoich klas.

Tylko dla klas, które są udostępniane przez publiczny interfejs COM, który powinien być niewielką liczbą klas należących do oryginalnej biblioteki.

Powiedział, że w innej sytuacji, o której wspomniałeś, kiedy już wiesz, że musisz wspierać COM jako obywatel pierwszej klasy od samego początku, myślę, że masz rację: należy zaoszczędzić kłopotów z dodaniem oddzielnej biblioteki lib i zaprojektować .NET lib z obsługą COM bezpośrednio.

Doktor Brown
źródło
(+1) Jednak „… która powinna być niewielką liczbą… oryginalnej biblioteki” jest tylko czasami prawdą. Istnieje wiele rodzajów projektów zaprojektowanych w „stylu biblioteki klas”, w których jedna klasa odpowiada jednemu publicznie widocznemu celowi, a klasy te komponują się, aby uzyskać ciekawą funkcjonalność. W takim przypadku może istnieć odwzorowanie jeden na jeden między liczbą klas .NET a liczbą klas międzyoperacyjnych COM.
rwong