1

I'm trying to figure out what the best method is for attaching a single key/value pair attribute to an enumeration where the key is my MerchantId and the value is the corresponding TransactionKey.

What I currently do is put a comma delimited string into a StringValueAttribute class:

Public Enum Merchants
    <StringValue("coke,faj80785hq+faf=-1=-jfa+">
        Coke = 0
    <StringValue("pepsi,adfji=-901jnas++fdj98ua")>
        Pepsi = 1
    <StringValue("drpepper,jk878-=+9kdkdja0=a=f--daj")>
        DrPepper = 2
End Enum

Public Property Merchant As Merchants


I pull out the key or MerchantId by calling .GetStringValue().Split(","c)(0):

Public ReadOnly Property MerchantId() As String
    Get
        Return Merchant.GetStringValue().Split(","c)(0)
    End Get
End Property


I pull out the value or TransactionKey by calling .GetStringValue().Split(","c)(1):

Public ReadOnly Property TransactionKey() As String
    Get
        Return Merchant.GetStringValue().Split(","c)(1)
    End Get
End Property

Is this the most efficient way to do this? Instead of StringValueAttribute, what about creating an attribute using a Dictionary(Of String, String) since it is a key/value pair list? Or String Array or List? Maybe something in LINQ? Or is it simply already efficient as it can be?

Steve Wilkes
  • 7,085
  • 3
  • 29
  • 32
Code Maverick
  • 20,171
  • 12
  • 62
  • 114

3 Answers3

1

I would suggest creating your own attribute class which takes both values in a type-safe manner and names them appropriately. Or, if not every item will have both values, create two separate attributes, one for each value.

Or better yet, don't use an Enum at all. Create your own class that takes all three values in the constructor and then create a class with shared properties for each item, as such:

Public Class Merchants
    Public Shared ReadOnly Property Coke() As Merchant
        Get
            Return _coke
        End Get
    End Property
    Private Shared _coke = New Merchant(0, "Coke", "faj80785hq+faf=-1=-jfa+")

    ...
End Class
Steven Doggart
  • 43,358
  • 8
  • 68
  • 105
  • Check out http://www.codeguru.com/vb/gen/vb_general/attributes/article.php/c6073/Creating-and-Using-Custom-Attributes-with-VBNET.htm – Steven Doggart May 04 '12 at 14:30
  • I don't know. That was written in 2003. There has got to be something more lightweight than that. I'm talking about efficiency here. – Code Maverick May 04 '12 at 14:34
  • If you are using attributes at all, it has to use reflection to read them. GetStringValue is doing the same thing, it's just wrapping it up nicely for you in a single method. I would think my second option would be more efficient than using attributes and reflection, but I couldn't say for sure without doing some speed testing on both options. I would think making the Merchant type a value type instead of a reference type would gain you a little performance, provided the data is small. – Steven Doggart May 04 '12 at 14:38
  • Could your second option be achieved with LINQ somehow? To me, it seems like Merchants should just be a List(Of Merchant). Or am I just looking at this too hard? – Code Maverick May 04 '12 at 14:57
  • I just assumed that if you were using an Enum, it was because you wanted a list of conveniently grouped and accessible constants, which is what my second suggestion accomplishes. So for instance, if you had a method that asked for a Merchant ID, you could pass it Merchants.Coke.Id. If it was in a list, you would have to first find the item in the list and then get the ID from it, and then pass the ID to the method. The other advantage of my suggestion is that if you have a method that needs both the merchant ID and description, you could simply have it ask for the entire Merchant object. – Steven Doggart May 04 '12 at 15:06
  • +1 for the effort. I like your second approach but I think @SteveWilkes' answer is exactly what I was looking for with caching to boot. – Code Maverick May 04 '12 at 15:39
1

You can use the following custom attribute and extension methods to get the values. Some things to note:

  • It's in C#, hope that's ok :)

  • The extension methods class caches the MerchantIds and TransactionIds in static scope, so should be pretty efficient.

  • You get the MerchantId by calling (e.g.) Merchants.Coke.GetMerchantId();.

  • You get the TransactionId by calling (e.g.) Merchants.Coke.GetTransactionId();.

Also, the extension methods don't bother checking that the Merchants value passed to them is valid, so you could break it by calling ((Merchants)76282).GetMerchantId().

[AttributeUsage(AttributeTargets.Field)]
public class MerchantDataAttribute : Attribute
{
    public MerchantDataAttribute(string merchantId, string transactionId)
    {
        this.MerchantId = merchantId;
        this.TransactionId = transactionId;
    }

    public string MerchantId
    {
        get;
        private set;
    }

    public string TransactionId
    {
        get;
        private set;
    }
}

public static class MerchantsExtensions
{
    private static readonly Dictionary<Merchants, MerchantDataAttribute> 
        _merchantsCache = CacheMerchantsCache();

    public static string GetMerchantId(this Merchants merchants)
    {
        return _merchantsCache[merchants].MerchantId;
    }

    public static string GetTransactionId(this Merchants merchants)
    {
        return _merchantsCache[merchants].TransactionId;
    }

    private static Dictionary<Merchants, MerchantDataAttribute> CacheMerchantsCache()
    {
        return Enum.GetValues(typeof(Merchants))
            .Cast<Merchants>()
            .Select(m => new
            {
                Merchant = m, 
                MerchantAttribute = GetMerchantAttribute(m)
            })
            .ToDictionary(m => m.Merchant, m => m.MerchantAttribute);
    }

    private static MerchantDataAttribute GetMerchantAttribute(Merchants merchant)
    {
        return typeof(Merchants)
            .GetMember(merchant.ToString())
            .First()
            .GetCustomAttributes(typeof(MerchantDataAttribute), inherit: false)
            .Cast<MerchantDataAttribute>()
            .First();
    }
}
Steve Wilkes
  • 7,085
  • 3
  • 29
  • 32
  • This looks like exactly what I was referring to, however, I can't seem to get it to convert to VB without errors. Those converters don't like LINQ as of yet. Getting stuck on the CacheMerchantsCache 's `.Select` part, as there is no `.Select` method. Same thing with GetMerchantAttribute's `.GetCustomAttributes`. – Code Maverick May 04 '12 at 15:25
  • Yes, this is an excellent implementation of my first suggestion. What part are you having trouble with getting into VB? – Steven Doggart May 04 '12 at 15:28
  • Ahh, whoops - I wrote it from memory and haven't tested it - I was missing a call to `.Cast()` in `CacheMerchantsCache()` and to `.First()` in `GetMerchantAttribute()`. I've updated my answer now :) – Steve Wilkes May 04 '12 at 15:30
  • Ok, that select is using an anonymous method (an in-lined method that has no name). VB has no equivalent. You'd have to create a normal, named, private method for that. – Steven Doggart May 04 '12 at 15:30
  • @SteveWilkes - That last edit did it. Ok ... I'm going to try this out. And just for clarification, in my Enum, I need to change `` to ``, correct? – Code Maverick May 04 '12 at 15:34
  • That's right, yeah - *e.g.* `Coke = 0` – Steve Wilkes May 04 '12 at 15:37
  • Awesome, ok. I'll mark this as the answer and reserve the right to hit you up with a comment if I have an issue =D Thanks so much. – Code Maverick May 04 '12 at 15:39
  • No problems, Scott, happy to help :) – Steve Wilkes May 04 '12 at 15:39
  • @SteveWilkes - Let me throw this twist at you. Let's say this information can't be kept in the code file due to transaction keys needing to be updated when they expire. So I've created a Merchants table where I store the `Id`, `DisplayName`, `MerchantId`, `TransactionKey`. Now, as I understand it, I can't create enumerations at runtime, so what would I do in this case where I need to pull the info from the database and basically dynamically create an enumeration? – Code Maverick May 04 '12 at 17:33
  • 1
    @SteveDog, VB 2010 has a syntax for Anonymous methods, its just a little ugly in comparison to the C# equivalent. It would look like `.Select(Function(x) Return New With{Merchant=x, MerchantAttribute = GetMerchantAttribute(x)})` I believe. I usually use the query syntax instead of the method syntax in VB because of that. – Kratz May 04 '12 at 18:00
  • @Kratz, hooray! I did not know that. I learned something today. Thanks! Unfortunately we aren't allowed to use 2010 yet at my job, so it doesn't do me any good for now :( – Steven Doggart May 04 '12 at 18:08
1

For any future visitors, I thought I'd post the VB version of the answer, since that's what I tagged the question with. Also, I had to do things slightly different due to VB requiring extensions to be inside of a Module.

Here is the Module:

(I used a lot of line continuation for easier readability. Also, for the sake of readability in this example I imported SomeClass so that I didn't have to type out that NameSpace)

Imports SomeClass

Module MerchantsExtensions

    Private ReadOnly MerchantsCache _
        As Dictionary(Of Merchants, MerchantDataAttribute) _
        = CacheMerchantsCache()

    Private Function CacheMerchantsCache() _
        As Dictionary(Of Merchants, MerchantDataAttribute)

        Return [Enum].GetValues(GetType(Merchants)) _
            .Cast(Of Merchants)() _
            .Select(Function(m) New With
            {
                .Merchant = m,
                .MerchantAttribute = GetMerchantAttribute(m)
            }) _
            .ToDictionary(Function(m) m.Merchant, _
                          Function(m) m.MerchantAttribute)

    End Function

    Private Function GetMerchantAttribute(merchant As Merchants) _
        As MerchantDataAttribute

        Return GetType(Merchants) _
            .GetMember(merchant.ToString()) _
            .First() _
            .GetCustomAttributes(GetType(MerchantDataAttribute), _
                                 inherit:=False) _
            .Cast(Of MerchantDataAttribute)() _
            .First()

    End Function

    <Runtime.CompilerServices.Extension()>
    Public Function GetMerchantId(merchants As Merchants) As String

        Return MerchantsCache(merchants).Id

    End Function

    <Runtime.CompilerServices.Extension()>
    Public Function GetTransactionKey(merchants As Merchants) As String

        Return MerchantsCache(merchants).TransactionKey

    End Function

End Module


Here is the implementation of the extension methods in a class I named SomeClass for this example:

Public Class SomeClass

    Public Enum Merchants
        <MerchantData("coke", "faj80785hq+faf=-1=-jfa+")>
            Coke = 0
        <MerchantData("pepsi","adfji=-901jnas++fdj98ua")>
            Pepsi = 1
        <MerchantData("drpepper","jk878-=+9kdkdja0=a=f--daj")>
            DrPepper = 2
    End Enum

    <AttributeUsage(AttributeTargets.Field)>
    Public Class MerchantDataAttribute : Inherits Attribute

        Public Sub New(merchantId As String, transactionKey As String)
            _Id = merchantId
            _TransactionKey = transactionKey
        End Sub

        Public Property Id() As String
        Public Property TransactionKey() As String

    End Class

End Class
Code Maverick
  • 20,171
  • 12
  • 62
  • 114