1

I have to structs both having e.g. "Id":

public struct User
{
    public int Id;
    public string Email;
}
public struct Computer
{
    public int Id;
    public string Name;
}

I'd like to make a template method to rewrite Id from one IList of Computers, Users and such to another.

I've tried below, but VS complains T does not contain a definition for Id:

    private static void RewriteIListIds<T>(ref IList<T> pre, IList<T> post)
    {
        if (post != null && post.Count > 0)
        {
            Assert.IsTrue(pre != null && pre.Count > 0);
            for (int i = 0; i < post.Count; i++)
            {
                T preElement = pre[i];
                T postElement = post[i];
                preElement.Id = postElement.Id;
                pre[i] = preElement;
            }
        }
    }

EDIT: Interesting ideas but I probably should have mention I'm testing a service which I really don't want and most probably can't really change.

EDIT2: Just for future references and to be more clear - I've probably made this problem more generic than it should be - User and Computer structs are what a Web Service (currently configured as SOAP) returns in an IList. [DataContract] and [Data Member] was removed from above example to make this problem a bit more generic.

Nux
  • 9,276
  • 5
  • 59
  • 72

4 Answers4

2

No. C# generics aren't C++ templates, basically. I would suggest that:

  • You stop exposing fields publicly
  • You stop using mutable structs
  • You stop using ref when you don't need to (see my article on parameter passing for more details)
  • You extract an interface with a read/write Id property
  • You implement that interface on two classes for User and Computer
  • You add a constraint of where T : IFoo to your generic method where IFoo is your new interface (with a better name, of course)
  • You can then remove the pre[i] = preElement; line of your method too...

Meta: Don't refer to a field as an attribute; the word attribute has a very specific meaning in .NET which is not the same as a field or property.

(Apologies for the slightly curt response - I don't have time to explain each point in detail right now.)

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • You where right about the ref but wrong about pre[1] = preElement (IList just doesn't when you change it's element in any other way). – Nux Oct 07 '11 at 08:10
  • @Nux: That's only because you're using a struct. When you change it to a class, you won't have to. Hence the "then" part of that sentence. – Jon Skeet Oct 07 '11 at 08:11
  • Structs are what it is used in the WS I'm testing and I can't really changed them. My bad that I didn't mention it at first. I've just made a lot of false assumptions hence the downvote. – Nux Oct 07 '11 at 09:46
  • @Nux: Just because you're using a web service doesn't mean you have to use mutable structs. You really should look into changing this ASAP - it *will* bite you repeatedly. Even if the web service somehow forces you to use "their" mutable structs when calling that service, I'd thoroughly recommend that you create more sensible types to convert that data to, rather than using them within your own code. Isolate the evil. – Jon Skeet Oct 07 '11 at 09:50
  • I'm not sure what would be the point of passing immutable structs with web service (assuming it's even possible). AFAIK you can't pass methods in a web service and I'm almost sure it's not possible when you use different technologies/languages on both sides (my case). – Nux Oct 07 '11 at 12:46
  • @Nux: The point is that having mutable structs causes confusion and woe. The web service itself can be implemented however it wants to be - I assume it's just SOAP or JSON somewhere when it comes to communications. There's no such *concept* as mutability at that level. So we're really just talking about the types generated to *support* this web service - and IMO it should *not* be generating mutable structs as they are anathema to usual .NET development. What's the web service in question? – Jon Skeet Oct 07 '11 at 12:48
  • It would be to much work and of little effect. We have to support (potentially) connections form Flex, HTML/Javascript and VS. – Nux Oct 07 '11 at 12:52
  • @Nux: It's not at all clear to me what "connections" you're talking about here. JavaScript isn't going to use your .NET types, is it? You need to separate out the transport representation of the service from the types generated to help use or implement that service. It would really help if you could update the question with a careful description of your context - at the moment it's too woolly to really help much. Please read http://tinyurl.com/so-hints – Jon Skeet Oct 07 '11 at 13:00
  • I've added more info. Maybe I've made this problem more generic (nomen omen) then I've should have. Still I alread got a working code for the problem I've defined. So thanks for your effort, but you answer was not the one I was looking for. – Nux Oct 07 '11 at 14:38
  • @Nux: It may not have been the one you were looking for, but I still think it's the best approach in the long term. If you keep using mutable structs, they will bite you again and again and again. – Jon Skeet Oct 07 '11 at 14:50
  • How would I use immutable struct in Flex after getting it from SOAP request? – Nux Oct 07 '11 at 14:51
  • @Nux: In Flex you wouldn't be using the .NET types at all, would you? I would expect each client environment to have a separate set of generated types, based on their own SOAP stack. If your structs aren't autogenerated, then I suspect that if you change them to classes, other SOAP clients won't be able to tell the difference *at all*. Again, it's absolutely imperative that you understand the difference between the protocol you're using and the types that are helping you to implement the web service protocol. – Jon Skeet Oct 07 '11 at 14:54
1

I agree with Jon: you probably don't have to do this, and it can be done in some other way. But if you really have to you can tell the method how the type gets or sets its id.

  public delegate int IdGetter<in T>(T holder);
  public delegate T IdSetter<T>(T holder, int newId);

  private static void RewriteIListIds<T>(IList<T> pre, IList<T> post, 
                                         IdGetter<T> getId, IdSetter<T> setId)
  {
     if (post != null && post.Count > 0)
     {
        for (int i = 0; i < post.Count; i++)
        {
           T preElement = pre[i];
           T postElement = post[i];
           int id = getId(preElement);
           postElement = setId(postElement, id);
           post[i] = postElement;
        }
     }
  }

To use it

RewriteIListIds<User>(aList, bList, u => u.Id, (u,id) => {u.Id = id; return u;});
Anders Forsgren
  • 10,827
  • 4
  • 40
  • 77
  • That almost works... any ideas why the setter doesn't work (the struct doesn't seem to change)? I'm using `idSetter(preElement, id);` – Nux Oct 06 '11 at 15:37
  • My example wouldn't work with value types as the would seem unchanged from the caller (they are copied when the setter method is called). Instead the setter delegate should return the modified object for it to work with value types. Example updated. – Anders Forsgren Oct 06 '11 at 21:45
  • You have some typos in your code, so I've pasted full test code here: http://paste.org/pastebin/view/39316. I get Error 1 Invalid variance: The type parameter 'T' must be covariantly valid on 'TestConsoleApplication1.Program.IdSetter.Invoke(T, int)'. 'T' is contravariant... Any ideas? – Nux Oct 07 '11 at 07:55
  • Turned out setter needed two types. Edited your code and it now works \o/ – Nux Oct 07 '11 at 08:07
  • 1
    Sorry about that, I was on the mac so I couldn't test the example. At least it compiles now. Again, I strongly recommend you use classes instead of structs, that way maybe you can use an interface. – Anders Forsgren Oct 07 '11 at 08:08
  • @AndersForsgren: In theory he could use an interface even with the struct. It would still be a bad idea though... mutable structs are simply evil. – Jon Skeet Oct 07 '11 at 08:35
  • Agreed. But using mutating interfaces on structs (i.e., you don't even see why your myObj.Id = newId doesn't mutate the object) is so evil it makes the practice of mutable structs feel like church! :) I just got an idea for a new custom FxCop rule. – Anders Forsgren Oct 07 '11 at 12:03
0

I assume that you have a C++ background? The C# feature you are using is called "generics".

Generics are not templates.

[...] You can think of templates as a fancy-pants search-and-replace mechanism. When you say DoIt<string> in a template, the compiler conceptually searches out all uses of “T”, replaces them with “string”, and then compiles the resulting source code. Overload resolution proceeds with the substituted type arguments known, and the generated code then reflects the results of that overload resolution.

[...] That’s not how generic types work; generic types are, well, generic. We do the overload resolution once and bake in the result. We do not change it at runtime when someone, possibly in an entirely different assembly, uses string as a type argument to the method

On another note, it is strongly recommended not to create mutable structs in C#.

Community
  • 1
  • 1
Ani
  • 111,048
  • 26
  • 262
  • 307
0

You need to constrain you generic so that the compiler knows something about type T. One way to do this is to create a parent object that contains the Id value, lets call it Thing, then have your two objects inherit from that. Then you can declare your generic so that T is constrained to children of Thing:

public class Thing
{
    public int Id;
}

public class User: Thing
{
    public string Email; 
}

public class Computer : Thing 
{    
    public string Name; 
} 

private static void RewriteIListIds<T>(ref IList<T> pre, IList<T> post) where T: Thing

Now the compiler knows that T must contain all the properties of Thing so it can assume there will be an Id field. If you don't want to use inheritance, the you can do the same thing with an interface and have each of your objects implelment that interface.

user957902
  • 3,010
  • 14
  • 18