Laden...

Überladungen reduzieren mit Extension Methods oder doch irgendwie anders?

Erstellt von UNeverNo vor 11 Jahren Letzter Beitrag vor 11 Jahren 2.444 Views
Thema geschlossen
UNeverNo Themenstarter:in
153 Beiträge seit 2007
vor 11 Jahren
Überladungen reduzieren mit Extension Methods oder doch irgendwie anders?

Wir haben hier eine 1.1er-GetXYZ-Methode, die 47 Überladungen beinhaltet, fast alle in der Art (filtere auf key1/value1, ..., keyn/valuen). Deutlich zu viele, wie ich meine.

Aufruf wäre bspw.


MyObject m = GetXYZ(iRow, sTableName, sKey1, sValue1, sKey2, sValue2, sKey3, sValue3, sKey4, sValue4, sKey5, sValue5);

Ich möchte diese nun reduzieren und bin auf der Suche nach der intuitivsten Strukturierung davon.

Man könnte es mit einem Dictionary<string, string> als Parameter definieren:


MyObject m = GetXYZ(iRow, sTableName, myDic);

Nachteil hieran wäre, dass ich immer ein myDic initialisieren muss und alle Key-Value-Paare hinzufügen müsste.

Ich könnte auch Extensionmethods für MyObject definieren:


m.Add(key1, value1);
m.Add(key2, value2);

Beim dann folgenden Aufruf von GetXYZ würde ich dann die Key-Value-Paare berücksichtigen. Nachteil hier wäre, dass die Extensions eben immer aufrufbar wären und nicht nur dann, wenn ich GetXYZ aufrufe. Man müsste also immer auf den Anwender vertrauen, dass er das Ding auch wieder cleart (oder das eben immer automatisch am Ende von GetXYZ tun, was auch nicht immer gewollt sein könnte).

Optionale Parameter gingen auch, allerdings empfinde ich die auch irgendwie als unsauber, hinzu kommt, dass die Anzahl der Key-Value-Paare auf n begrenzt ist (analog zum Ist-Zustand).

Tja, wie mache ich das nun? Gibt doch bestimmt noch coole andere Sprachfeatures von C# 4.0 die mir das erleichtern, oder?

Grüße,
Chris

"Wenn Architekten genauso bauen würden, wie Programmierer programmieren, dann würde der erste Specht, der vorbeikommt, die Zivilisation zerstören." (Steven Weinberg)

6.862 Beiträge seit 2003
vor 11 Jahren

Hallo,

du kannst nen Sprachfeature verwenden, welches es seit .Net 1.1 gibt: params

Baka wa shinanakya naoranai.

Mein XING Profil.

2.891 Beiträge seit 2004
vor 11 Jahren

Und ein Parameter-Objekt gefällt dir nicht?


public class GetXyzParams
{
   public int Row { get; set; }
   public string TableName{ get; set; }
   public string Key1 { get; set; }
   public string Value1 { get; set; }
}

public void GetXYZ(GetXyzParams parameters) { ... }

Die kannst du dann auch einfach via Objektinitialisierer aufrufen:


GetXYZ(new GetXyzParams{ Row = 1,TableName = "Customer" });

Oder, wenn du (nur) deine Key-Value-Paare beliebig oft übergeben möchtest:


public void GetXYZ(int row,string tableName,params KeyValuePair[] keyValuePairs) { ... }

3.825 Beiträge seit 2006
vor 11 Jahren

Wie wärs mit Fluid Interfaces, ungefähr so

clsDataQuery qry = new clsDataQuery("fa").fa_adnr().IsEqual(10101).And().fa_admatch().IsLike("H%");
// oder so
MyObject m = GetXYZ(iRow, sTableName).AddKey(key1, value1).AddKey(key2, value2);

?

Grüße Bernd

Workshop : Datenbanken mit ADO.NET
Xamarin Mobile App : Finderwille Einsatz App
Unternehmenssoftware : Quasar-3

UNeverNo Themenstarter:in
153 Beiträge seit 2007
vor 11 Jahren

Hallo,

du kannst nen Sprachfeature verwenden, welches es seit .Net 1.1 gibt: params

Kenn ich, finde ich aber bei Erweiterungen problematisch, da dann keine Überladung hinten angefügt werden kann.

Und ein Parameter-Objekt gefällt dir nicht?

  
public class GetXyzParams  
{  
   public int Row { get; set; }  
   public string TableName{ get; set; }  
   public string Key1 { get; set; }  
   public string Value1 { get; set; }  
}  
  
public void GetXYZ(GetXyzParams parameters) { ... }  
  

Die kannst du dann auch einfach via Objektinitialisierer aufrufen:

  
GetXYZ(new GetXyzParams{ Row = 1,TableName = "Customer" });  
  

Oder, wenn du (nur) deine Key-Value-Paare beliebig oft übergeben möchtest:

  
public void GetXYZ(int row,string tableName,params KeyValuePair[] keyValuePairs) { ... }  
  

Wie auch oben geschrieben kenne ich es, auch wenn ich an die Auslagerung in eine Klasse nicht nachgedacht habe, allerdings gefällt mir der Aufruf irgendwie auch nicht hundertprozentig:


MyObject m = GetXYZ(iRow, sTable, new KeyValuePair(key1, value1), new KeyValuePair(key2, value2), KeyValuePair(key3, value3), ....);

Wie wärs mit Fluid Interfaces, ungefähr so

clsDataQuery qry = new clsDataQuery("fa").fa_adnr().IsEqual(10101).And().fa_admatch().IsLike("H%");  
// oder so  
MyObject m = GetXYZ(iRow, sTableName).AddKey(key1, value1).AddKey(key2, value2);  
  

?

Grüße Bernd Sowas wie die 2. Lösung würde ich persönlich anstreben, allerdings hapert es bei der Umsetzung ein wenig (AddKey müsste ja jeweils MyObject zurückgeben). Wie ich allerdings grad sehe müsste ich wohl eigentlich immer nur

return this;

in den entsprechenden Methoden zurückgeben, was an und für sich ja auch logisch ist.
Immer schön, wenn man einen Namen zu dem hat, was man sucht 👍

Allerdings müsste ich dann in AddKey irgendwie einschränken an welchen Methoden es aufrufbar ist, ansonsten habe ich ja wieder das gleiche Problem wie bei Extensions.

Grüße,
Chris

"Wenn Architekten genauso bauen würden, wie Programmierer programmieren, dann würde der erste Specht, der vorbeikommt, die Zivilisation zerstören." (Steven Weinberg)

C
258 Beiträge seit 2011
vor 11 Jahren

Wie schon talla gesagt hat am einfachsten wäre params:


        private void GetXYZ(int row, string tableName, params KeyValue[] keyValuePairs)
        {
            foreach (var item in keyValuePairs)
            {

            }
        }

    public struct KeyValue
    {
        private string key;
        private string value;

        
    }

Ich versteh dein Problem mit überladenungen hinten anfügen gerade nicht,
ja die KeyValuePairs müssen immer am ende stehen das sollte aber doch kein Problem sein oder?

2.891 Beiträge seit 2004
vor 11 Jahren

Wie auch oben geschrieben kenne ich es, auch wenn ich an die Auslagerung in eine Klasse nicht nachgedacht habe, allerdings gefällt mir der Aufruf irgendwie auch nicht hundertprozentig.

Vermischt du hier die Auslagerung in eine Klasse und die params-Möglichkeit? Das waren zwei verschiedene, voneinander unabhängige Vorschläge von mir.

Was gefällt die an der Klassenvariante mit Objektinitialisierern nicht?

D
216 Beiträge seit 2009
vor 11 Jahren

Vielleicht gefällt dir dann ja sowas:

public class Filter {
  List<KeyValuePair<string, string>> _filter;
  
  private int _row;
  private string _tableName;
  
  public Filter(int row, string tableName) { 
    _filter = new List<KeyValuePair<string, string>>();
	_row = row;
	_tableName = tableName;
  }

  public Filter Add(string key, string value) {
    return Add(new KeyValuePair<string, string>(key, value));
  }

  public MyObject Get() { 
    return new MyObject.Get(_row, _tableName, _filter.AsEnumerable());
  }
}

public class MyObject {
  public static MyObject Get(int row, string tableName, IEnumerable<KeyValuePair<string, string>> filter) {
    //...
  }
  
  public static Filter GetWithFilter(int row, string tableName) {
    return new Filter(row, tableName);
  }
}

//benutzen:

MyObject x = MyObject.GetWithFilter(42, "xxx").Add("test", "123").Add("blubb", "bla").Add("foo", "bar").Get();

Darth Maim

C
2.122 Beiträge seit 2010
vor 11 Jahren

Ich würde auch zu einzelnen Add-Operationen tendieren.
Also das da
m.Add(paar 1);
m.Add(paar 2);

Zig Parameter in einem Aufruf übergeben finde ich nicht sehr übersichtlich. Vor allem wenn es key-value Paare sind, wärs für mich anschaulicher wenn die Wertepaare einzeln in einem Aufruf erkennbar wären.

D
216 Beiträge seit 2009
vor 11 Jahren

Man kann ja auch ohne Probleme folgendes schreiben:

MyObject x = MyObject.GetWithFilter(42, "xxx")
                     .Add("test", "123")
                     .Add("blubb", "bla")
                     .Add("foo", "bar")
                     .Get();

Darth Maim

49.485 Beiträge seit 2005
vor 11 Jahren

Hallo UNeverNo,

Man müsste also immer auf den Anwender vertrauen, dass er das Ding auch wieder cleart

warum sollte Add eine Methode von MyObject sein? Es geht doch offensichtlich darum, einen Filter zu definieren. Also

MyObject m = GetXYZ (filter);

Wobei der Filter seine eigenen fluent Methoden haben kann. Im Ergebnis

MyObject m = GetXYZ (new Filter (iRow, sTableName).Add (sKey1, sValue1).Add (sKey2, sValue2)... );

Das sorgt dann für klare Trennung und beseitigt das Problem von Darth Maims Vorschlag, dass sich MyObject und Filter gegenseitig kennen müssen. Es reicht, wenn MyObject Filter kennt. Außerdem vermeidet man die Konstruktion mit dem zusätzlichen Get am Ende.

herbivore

UNeverNo Themenstarter:in
153 Beiträge seit 2007
vor 11 Jahren

Wobei der Filter seine eigenen fluent Methoden haben kann. Im Ergebnis

MyObject m = GetXYZ (new Filter (iRow, sTableName).Add (sKey1, sValue1).Add (sKey2, sValue2)... );  
  

Das sorgt dann für klare Trennung und beseitigt das Problem von Darth Maims Vorschlag, dass sich MyObject und Filter gegenseitig kennen müssen. Es reicht, wenn MyObject Filter kennt. Außerdem vermeidet man die Konstruktion mit dem zusätzlichen Get am Ende.

Hi herbivore,
das Get stört mich persönlich am Ende auch, allerdings bin ich mir nicht sicher bzw. sehe momentan nicht, wie ich Deinen Vorschlag effektiv umsetze. Das iRow und sTableName sind Überladungen der GetXYZ unabhängig vom Filter (die paar würde ich auch belassen). Wenn ich es nun so wie Du vorschlägst umsetze, dann hätte ich wieder eine Überladungsebene mehr, was die Anzahl selbiger verdoppelt. Korrigiere mich, wenn ich es falsch verstehe.

Parallel dazu mal den aktuellen Stand meiner Bemühungen:


    public class MyObject
    {
        private Dictionary<string, object> myDic = new Dictionary<string, object>();

        public Dictionary<string, object> Dic
        {
            get { return myDic; }
            set { myDic = value; }
        }
    }

    public static class FluentFactory<T>
    {
        public static IFluentMethods<T> Create(MyObject myObject)
        {
            return new Fluent<T>(myObject);
        }
    }

    public class Fluent<T> : IFluentMethods<T>
    {
        private MyObject _myObject;

        public Fluent(MyObject myObject)
        {
            _myObject = myObject;
        }

        public IFluentMethods<T> Where(string key, object value)
        {
            _myObject.Dic.Add(key, value);
            return this;
        }

        public T Get(string key)
        {
            return (T)Convert.ChangeType(this._myObject.Dic[key], typeof(T));
        }
    }

    public interface IFluentCreate<T>
    {
        IFluentMethods<T> Create(MyObject myObject);
    }

    public interface IFluentMethods<T>
    {
        IFluentMethods<T> Where(string key, object value);
        T Get(string key);        
    }

Hier ärgere ich mich genau mit der Rückgabe herum. In meinem Test hier muss ich schon bei der Factory-Initalisierung einen Datentyp angeben, eigentlich würde ich dies aber nur beim Get machen wollen.

var fluentObject = FluentFactory<string>.Create(myObject).Where("1", "5").Where("2", "7").Where("3", "11");
string s = fluentObject.Get("2");

Vielleicht nochmal ein wenig ausführlicher, was ich hier machen will. Ich habe ein altes 1.1-Objekt und will einfacher und intuitiver darauf zugreifen, fluent interfaces scheinen mir dafür eine schöne Umsetzung zu sein. Ich will dieses Objekt nun wrappen und die in ihm enthaltenen Unterobjekte in verschiedene Datentypen (string, bool, int, double) casten.

Grüße,
Chris

"Wenn Architekten genauso bauen würden, wie Programmierer programmieren, dann würde der erste Specht, der vorbeikommt, die Zivilisation zerstören." (Steven Weinberg)

49.485 Beiträge seit 2005
vor 11 Jahren

Hallo UNeverNo,

ich sehe dein Problem nicht und mir scheint du treibst da einen unnötigen Riesenaufwand. Wenn du iRow und sTableName als Parameter von GetXYZ belassen willst bitte, dann eben:

MyObject m = GetXYZ (iRow, sTableName, new Filter ().Add (sKey1, sValue1).Add (sKey2, sValue2)... );

Wenn du den bestehenden Code minimal-invasiv ändern willst, braucht du keinen Wrapper, keine Factory, keine Interfaces, nichts mit Fluent im Namen, sondern wirklich nur eine Klasse Filter, mit einen leeren Konstruktor und einer (fluent) Add-Methode. Mehr nicht (ok, noch eine Methode o.ä. zum Auslesen der Key-Value-Werte). Und das solltest du alleine umgesetzt bekommen.

herbivore

UNeverNo Themenstarter:in
153 Beiträge seit 2007
vor 11 Jahren

Was mir an dem new Filter einfach nicht so gefällt ist eine Verdopplung der Überladungen, wie ich es oben schon mal versuchte zu beschreiben.
Habe ich standardmäßig

GetXYZ()
GetXYZ(iRow)
GetXYZ(iRow, sTable)

hätte ich nun

GetXYZ()
GetXYZ(oFilter)
GetXYZ(iRow)
GetXYZ(iRow, oFilter)
GetXYZ(iRow, sTable)
GetXYZ(iRow, sTable, oFilter)

Und das würde ich - wenn möglich - verhindern. Oder habe ich mich gerade gedanklich verrannt? Dass ich das selbst umsetzen kann ist klar und das will ich auch 😉

Alternativ könnte man es wohl auch mit optionalen Parametern machen, aber empfindet ihr/empfindest du das als eine saubere Alternative?

Grüße,
Chris

"Wenn Architekten genauso bauen würden, wie Programmierer programmieren, dann würde der erste Specht, der vorbeikommt, die Zivilisation zerstören." (Steven Weinberg)

49.485 Beiträge seit 2005
vor 11 Jahren

Hallo UNeverNo,

nein, du hast eine Reduzierung der Überladungen, denn statt

GetXYZ(...)
GetXYZ(..., key1, value1)
GetXYZ(..., key1, value1, key2, value2)
GetXYZ(..., key1, value1, key2, value2, key3, value3)
usw.

brauchst du nur noch

GetXYZ(..., oFilter)

Wenn der Filter nicht nötig ist, kannst du einfach einen leeren Filter übergeben, also

GetXYZ(..., new Filter ())

das heißt, die Überladung

GetXYZ(...)

brauchst du nur aus Komfortgründen, um dir die Erzeugung des leeren Filters sparen zu können. Aber selbst dann hast du pro Grundüberladung (damit meine ich die drei Gruppen, je nachdem ob man für das "..." innerhalb der Klammern leer, "iRow", oder "iRow, sTable" einsetzt) nur zwei statt n Überladungen.

Nach meinem Geschmack sind wir jetzt wirklich bei [Hinweis] Wie poste ich richtig? Punkt 1.1.1 angekommen. Sollte ich etwas wesentliches übersehen, schreib eine PM.

herbivore

Thema geschlossen