Laden...

Generische Typeinschränkung für TService und TInterface formulieren

Erstellt von ErfinderDesRades vor 5 Jahren Letzter Beitrag vor 5 Jahren 1.757 Views
ErfinderDesRades Themenstarter:in
5.299 Beiträge seit 2008
vor 5 Jahren
Generische Typeinschränkung für TService und TInterface formulieren

Hi!

Ich bin derzeit mit Wcf-Webservices befasst, und immer wenn ich einen instanziere muss ich auch einen ObjectContext instanzieren, und myWebService.InnerChannel übergeben.
.InnerChannel ist nichtgenerische Property der Service-Basisklasse, also von ServiceBase(Of TInterface).

Ich würde nun gerne eine generalisierte Methode coden, die gleich beides erstellt: den WcfService und den ObjectContext, und die für alle möglichen WcfService verwendbar wäre - prinzipiell sowas:

public void InitService<TService>()
where TService : ServiceBase<?>, new {
   _Service = new TService();
   _Context = new ObjectContext(_Service.InnerChannel);
   //...
}

Aber leider weiss ich nicht, wie ich die ServiceBase<TInterface> als TypParameter-Einschränkung formuliere, weil ich TInterface nicht habe.
Tatsächlich brauche ich TInterface auch nicht, denn ich will ja auf den nichtgenerische Property .InnerChannel zugreifen - hat ja mit dem Interface nix zu tun.

Geht das überhaupt?

(Übrigens "ObjectContext" ist nicht der richtige Datentyp-Name des 2.Objektes, das ich erstellen muss - ist mir entfallen. Aber ich hoffe das prinzipielle Problem ist klargeworden?)

Der frühe Apfel fängt den Wurm.

4.939 Beiträge seit 2008
vor 5 Jahren

Hat denn die generische Klasse ServiceBase<T> eine nichtgenerische Basisklasse oder Interface, welche du dann als Typeinschränkung verwenden kannst?
Oder ist das denn eine selbstgeschriebene Klasse und du kannst diese dann entsprechend anpassen?

Ansonsten ginge noch:


public void InitService<TService, TInterface>()
where TService : ServiceBase<TInterface>, new()
{
  // ...
}

6.911 Beiträge seit 2009
vor 5 Jahren

Hallo ErfinderDesRades,

im Kontext von WCF kannst du dafür auch Reflektion anwenden und so das Objekt erstellen.
Wenn die ConstructorInfo und PropertyInfo in einem Feld gecached wird, so ist es laufzeitmäßig nicht so schlimm und WCF verwendet intern auch jede Menge Reflektion, zumal letztlich die Daten mit (großer) Latenz übers Netzwerk geschickt werden.

Kannst du etwas mehr über den Kontext deines Problems berichten, denn so verstehe ich das Vorhaben nicht ganz und denke dass es alternative Wege dafür geben mag.

Übrigens "ObjectContext" ist nicht der richtige Datentyp-Name des 2.Objektes OperationContext?

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

ErfinderDesRades Themenstarter:in
5.299 Beiträge seit 2008
vor 5 Jahren

Dassis etwas mühsam, weil es um Codes in VB geht, die auf Arbeit sind (und ich krankgeschrieben).
Aber ich habs jetzt mal vom Prinzip aussm Kopf glaub ziemlich korrekt hingebastelt.

Also auf ARbeit haben wir eine Anwendung, die ca. 50 InterfaceMember in 10 WcfServices konsumiert, und das ist eine wahre "DoRepeatYourself"-Orgie, schlimmer noch, die Selbstwiderholungen sind teilweise auch noch inkorrekt implementiert. (hysterisch gewachsener Code).

Also wenn man nur einen besch... String abrufen will muss man folgende Übung absolvieren:


      static string GetFoo() {
         // dazu Logging, Localisation, Errorhandling, response-Header-Auswertung - in immer derselben Implementierung
         using (var myClient = new ServiceClient())
         using (var scope = new OperationContextScope(myClient.InnerChannel)) {
            OperationContext.Current.OutgoingMessageProperties["Autentication"] = _AutenticationToken;
            return myClient.foo().Data;
         }
      }

Wie gesagt (im Comment angedeutet), das eigentliche Code-Gebrabbel ist hier weggelassen, realiter ists das 3-fache, und ist immer dasselbe.

Das Gebrabbel habich nun isoliert - der Pattern heisst glaub "Function höherer Ordnung einführen":


      static string GetFoo2() {
         return Execute((ServiceClient clnt) => clnt.foo().Data);
      }

      static TResult Execute<TService, TResult>(Func<TService, TResult> fnc) where TService : IDisposable, new() {
         // dazu Logging, Localisation, Errorhandling, response-Header-Auswertung - einmal implementiert, und richtig
         using (var myClient = new TService()) {
            IClientChannel chnl = GetChannelFromClient(myClient);
            using (var scope = new OperationContextScope(chnl)) {
               OperationContext.Current.OutgoingMessageProperties["Autentication"] = _AutenticationToken;
               return fnc(myClient);
            }
         }
      }

Wie du siehst - GetFoo2 macht jetzt nur noch, was man denkt was GetFoo2 im Kern zu machen hat - alles annere ist an einem Punkt konzentriert und sauber.
Bisserl unsauber halt noch das GetChannelFromClient(), weil das hampelt - wie du dir auch gedacht hast - mit Reflection rum, bzw. mit dynamic.

Jo, und da bin ich bisserl frustriert, dass ich die TypEinschränkung nicht so formulieren kann, dass ich normal auf myClient.InnerChannel zugreifen kann.
Weil eigentlich sind ja alle notwendigen TypInformationen gegeben.

Es ist also einerseits ein konkretes Problem, andererseits ein abstraktes: Es scheint mir ein Mangel der Sprache zu sein, weil prinzipiell wäre hier doch die Möglichkeit gegeben, auch Polymorphie bereitzustellen.
Nämlich die nicht-generischen Member eines generischen Typs könnte man doch eiglich zugreifbar machen, auch ohne dass der konkrete TypParameter bekannt ist.

Man kanns auch als DesignFehler der ServiceBase<T> - Klasse sehen: Täte die von einer nichtgenerischen ServiceBase erben, welche den .InnerChannel veröffentlichte, wäre ich ebenfalls nu der Notwendigkeit enthoben herumzureflecten.

Der frühe Apfel fängt den Wurm.

16.835 Beiträge seit 2008
vor 5 Jahren

Ich hab schon Jahre nichts mehr mit WCF am Hut gehabt, aber ich meine der generische Weg von WCF war über die ChannelFactory; nicht über den ServiceClient.

Ist für mich aber prinzipiell ein korrektes Verhalten der Sprache.

P
1.090 Beiträge seit 2011
vor 5 Jahren

Hi ErfinderDesRades,

wie machst du nicht eine Abstrakte Generische Basis Klasse, die die Methode zum Abrufen der Daten Public anbietet. Und eine Protetec Methode anbietet, die überschrieben werden muss.

Jetzt mal frei und schnell unterschriebenen

abstract Service
{

protected abstract T GetSpezialFoo(ServiceClient client);

public T GetFoo()
{

 // dazu Logging, Localisation, Errorhandling, response-Header-Auswertung - einmal implementiert, und richtig
         using (var myClient = new TService()) {
            IClientChannel chnl = GetChannelFromClient(myClient);
            using (var scope = new OperationContextScope(chnl)) {
               OperationContext.Current.OutgoingMessageProperties["Autentication"] = _AutenticationToken;
               return GetSpezialFoo(myClient); // <-- Hier wird dann die in den Abgeleitetet Klassen Implementierte Methode aufgerufen.
            }
         }
 

}

}


Sollte man mal gelesen haben:

Clean Code Developer
Entwurfsmuster
Anti-Pattern

ErfinderDesRades Themenstarter:in
5.299 Beiträge seit 2008
vor 5 Jahren

Hätte das einen Vorteil gegenüber dem Ansatz, den ich bereits habe?
Das Feine an meim Ansatz ist ja, dass ich einen Datenabruf an den Service absetzen kann, meist mit einem Einzeiler.
Wie sähe der Datenabruf bei deim Ansatz aus?

Der frühe Apfel fängt den Wurm.

4.939 Beiträge seit 2008
vor 5 Jahren

Hattest du meinen Beitrag gelesen, besonders den Code mal ausprobiert?


static string GetFoo2()
{
    return Execute<ServiceClient, ITest, string>((ServiceClient clnt) => clnt.foo().Data);
}

static TResult Execute<TService, TInterface, TResult>(Func<TService, TResult> fnc)
    where TService : ServiceBase<TInterface>, IDisposable, new()
{
   _Service = new TService();
   _Context = new ObjectContext(_Service.InnerChannel);
    // ...
}

Leider kriege ich dann die manuelle Angabe der Typparameter nicht eliminiert, s.a. Test Ideone-Code.

ErfinderDesRades Themenstarter:in
5.299 Beiträge seit 2008
vor 5 Jahren

jo - habich gesehen, und wie das als Aufruf rauskommt auch verstanden.
Gefällt mir aber nicht, 3 TypParameter angeben zu müssen - da bleib ich lieber bei der Reflection/dynamic - Lösung.

Wie gesagt: Meine Kernfrage war eher prinzipieller Natur, ob in derlei gelagerten Fällen eine TypParameter-Einschränkung möglich ist, die nicht den TypParameter der Basisklasse mitliefern muss.
Dassis geklärt - ist nicht möglich.

Der frühe Apfel fängt den Wurm.

4.939 Beiträge seit 2008
vor 5 Jahren

Ich habe noch ein bißchen getestet. Folgender Code funktioniert (also nur 1 Parameter mehr angeben):


static string GetFoo2()
{
    return Execute((ServiceClient clnt, ITest t) => clnt.foo().Data);
}

static TResult Execute<TService, TInterface, TResult>(Func<TService, TInterface, TResult> fnc)
    where TService : ServiceBase<TInterface>, IDisposable, new()
{
   _Service = new TService();
   _Context = new ObjectContext(_Service.InnerChannel);
    // ...
}

(s.a. geänderter Ideone-Code)

Edit:
Oder ein eigenes Interface definieren und dieses dann in den abgeleiteten Klassen implementieren (auch hier zwar Copy&Paste, aber nur einmal je Klasse):


interface IMyBase
{
	Channel MyInnerChannel { get; }	
}
	
class ServiceClient : ServiceBase<ITest>, IMyBase
{
	public Channel MyInnerChannel { get { return base.InnerChannel; } }
}

static TResult Execute<TService, TResult>(Func<TService, TResult> fnc)
        where TService : IMyBase, IDisposable, new()
{
    	TService service = new TService();
    	var channel = service.MyInnerChannel;
        // ...
}

(auch hier wieder ein getester Ideone-Code)

Beides erscheint mir sinnvoller, als mittels Reflection zu arbeiten.