0

I am writing a wrapper around the driver of a rare database. Specifically, I am implementing raw query code, so I naturally need as much speed as possible. This is going to be implemented in a large eCommerce solution.

This has put me off dynamic types and casting. I want direct access to the actual data type.

The functionality I need is to have a base parameter class with general fields, and a series of child classes with one specifically typed field each (one for each database type, which are int, long, double, string, byte[], object, and list, plus list of each of these types). The C# type should match the database type.

This is not straightforward because in my implementation, the calling code needs to see the base type, but base types can't see a child's fields.

Here is the code I have so far:

private abstract class GlobalQueryParam
{
    public readonly string Low;
    // Val must be correctly typed
    public abstract object Val; // requires casting; don't want, but works
    protected string dbVal = "";        

    public string GetDBVal()
    {
        return this.dbVal;
    }

    public abstract bool SetDBVal(string value);

    public GlobalQueryParam(string low)
    {
        this.Low = low;
    }
}

public class GlobalQueryParamInt : GlobalQueryParam
{
    /// <summary>
    /// Represents the current literal value. Used in traversal actions.
    /// </summary>
    public override int Val;

    public int Parse(string dbVal)
    {
        return (this.Val = int.Parse(dbVal));
    }

    public override bool SetDBVal(string value)
    {
        if (value != "")
        {
            this.dbVal = value;
            return int.TryParse(value, out this.Val);
        }

        return false;
    }

    public GlobalQueryParamInt(string low = "") : base(low) { }
}

In my implementation, I have to declare an array of the base class, which is throwing a spanner in the works.

I am sure there is a better way to do this. I looked at interfaces, but they too need the type, and I couldn't get generics to work because of the array of base types sharing a common type.

If I change the aforementioned array, I introduce other complexities which are circular in nature.

What is the most efficient way to implement what I am looking for, or as close to it as possible?

EDIT Implementation (doesn't work with generics):

    GlobalQueryParam[] trail = new GlobalQueryParam[dataPos+1]; // DOESN'T WORK
    trail[0] = new GlobalQueryParamInt((this.TestParams[0].QueryLow - 1).ToString());
    trail[1] = new GlobalQueryParamInt((this.TestParams[1].QueryLow - 1).ToString());
    trail[2] = new GlobalQueryParamInt((this.TestParams[2].QueryLow - 1).ToString());
    trail[3] = new GlobalQueryParamInt((this.TestParams[3].QueryLow - 1).ToString());
    trail[4] = new GlobalQueryParamInt((this.TestParams[4].QueryLow - 1).ToString());
    trail[5] = new GlobalQueryParamInt((this.TestParams[5].QueryLow - 1).ToString());
    trail[6] = new GlobalQueryParamInt((this.TestParams[6].QueryLow - 1).ToString());
    trail[7] = new GlobalQueryParamInt((this.TestParams[7].QueryLow - 1).ToString());
IamIC
  • 17,747
  • 20
  • 91
  • 154
  • What is the problem with an array of the base class? Is your concern the "Parse" method? – dumdum Sep 29 '12 at 14:44
  • The base class can't see the typed field in descendant classes. So I either loose access (which is obviously unworkable), or they all have to be the same type and get cast. That means boxing and unboxing, which is a huge performance hit on millions of int's per second. – IamIC Sep 29 '12 at 14:46
  • The Parse method is always passed a string, but could output int, long, double, array[], List, etc. – IamIC Sep 29 '12 at 14:47
  • 3
    Did you consider using [generics](http://msdn.microsoft.com/en-us/library/512aeb7t%28v=vs.100%29.aspx)? – Douglas Sep 29 '12 at 14:51
  • @Douglas I tried them, but had the same problem because the array of parameters expects a specific type, but obviously the generics are multiple types. – IamIC Sep 29 '12 at 14:55

3 Answers3

2

You want to use Generics

public abstract GlobalQueryParam{}

public abstract class GlobalQueryParam<TValue>: GlobalQueryParam where TValue : struct
{
    public readonly TValue Low;
    public TValue Val; 
    protected TValue dbVal = default(TValue);

    public TValue GetDBVal()
    {
        return  dbVal;
    }

    public abstract bool SetDBVal(TValue value);

    public abstract TValue Parse(string dbVal);

    public GlobalQueryParam(TValue low)
    {
        this.Low = low;
    }
}

public class GlobalQueryParamInt : GlobalQueryParam<int>
{

    public int Parse(string dbVal)
    {
        return (this.Val = int.Parse(dbVal));
    }

    public override bool SetDBVal(int value)
    {

        return true;
    }

    public GlobalQueryParamInt(int low = 0) : base(low) { }
}

EDIT: Create a base class for this and a collection class based on this post Collection of generic types

Community
  • 1
  • 1
dumdum
  • 838
  • 5
  • 10
  • Thanks. The field I am most concerned about is Val. This is the typed field. – IamIC Sep 29 '12 at 14:53
  • Sorry, I fixed it. I missed the most important property to use with the generic type! – dumdum Sep 29 '12 at 14:55
  • This is basically what I tried earlier (although I don't know what the struct limiter is doing when the child is a class). But it leaves the problem I mentioned of the array. The array is an array of fields, hence different types. I would then have to cast the parameters themselves, which is the circular problem I mentioned. – IamIC Sep 29 '12 at 14:59
  • Ps. dbVal is always a string. – IamIC Sep 29 '12 at 15:04
  • 1
    I edited this to link to how to use a base class to use for the collection – dumdum Sep 29 '12 at 15:20
  • I tried this, but then can't see any of the fields. For e.g. SetDBVal isn't visible. – IamIC Sep 29 '12 at 15:37
1

Create an interface with the methods/properties you need:

public interface IGlobalQueryParam { 

  string ToString();

  . . . 

} 

Then use the generic classes as in damdum answer, implementing the interface:

private abstract class GlobalQueryParam<TValue> : IGlobalQueryParam where TValue : struct {   
  public readonly TValue Low;   
  public TValue Val;    
  protected TValue dbVal = default(TValue);   

  public TValue GetDBVal()   
  {   
    return  dbVal;   
  }   

  public override string ToString()
  {
    return Val.ToString();
  } // ToString

  . . . implement IGlobalQueryParam . . .

}  

public class GlobalQueryParamInt : GlobalQueryParam<int> {

  . . . 
}

and then create and use arrays (or lists etc) of the interface:

IGlobalQueryParam[] trail = new IGlobalQueryParam[dataPos+1];
trail[0] = new GlobalQueryParamInt((this.TestParams[0].QueryLow - 1).ToString());    
trail[1] = new GlobalQueryParamInt((this.TestParams[1].QueryLow - 1).ToString());    
trail[2] = new GlobalQueryParamInt((this.TestParams[2].QueryLow - 1).ToString());    
trail[3] = new GlobalQueryParamInt((this.TestParams[3].QueryLow - 1).ToString());    
trail[4] = new GlobalQueryParamInt((this.TestParams[4].QueryLow - 1).ToString());    
trail[5] = new GlobalQueryParamInt((this.TestParams[5].QueryLow - 1).ToString());    

It is still not possible to access the underlying value of each item without boxing it (i.e. with a IGlobalQueryParam property returning object), but it is possible to create methods and properties of the interface that do what's needed on the underlying values withut boxing - for example to convert the values to a string as in the example above.

MiMo
  • 11,793
  • 1
  • 33
  • 48
  • Thanks. How to I implement the generic Val in the interface? Without implementation, it's invisible. – IamIC Sep 29 '12 at 15:28
  • 1
    @IanC: you cannot without boxing - but you can implement other operations on the value avoiding boxing - I expanded the answer a little bit. – MiMo Sep 29 '12 at 20:16
  • In the end, I reverted to my original code, which casts and boxes the values to and from object. Generics added nothing because object inheritance implemented the desired type. And interface added nothing once generics were removed. – IamIC Sep 30 '12 at 00:59
0

In the end, I reverted to my original code, which casts and boxes the values to and from object. Generics added nothing because object inheritance implemented the desired type. And interface added nothing once generics were removed.

IamIC
  • 17,747
  • 20
  • 91
  • 154